bonus_04_inversion.cpp 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. #include <cstdio>
  2. #include <cerrno>
  3. #include <sstream>
  4. #include <string>
  5. #include <iostream>
  6. #include <chrono>
  7. #include "simple/support/enum.hpp"
  8. #include "simple/geom/algorithm.hpp"
  9. #include "simple/graphical/initializer.h"
  10. #include "simple/graphical/pixels.hpp"
  11. #include "simple/graphical/software_window.h"
  12. #include "simple/graphical/algorithm/blit.h"
  13. #define STB_IMAGE_IMPLEMENTATION
  14. #include "external/stb_image.h"
  15. using namespace std::string_literals;
  16. using namespace simple::graphical;
  17. using namespace color_literals;
  18. using rgba_pixels = pixel_writer<rgba_pixel, pixel_byte>;
  19. using simple::support::to_integer;
  20. float2 invert(float2 in, float2 center, float radiance)
  21. {
  22. auto relative = in - center;
  23. return center + relative * radiance/relative.quadrance();
  24. }
  25. const float tau = 2*std::acos(-1);
  26. surface stb_load_surface(const char* filename);
  27. enum class commands
  28. {
  29. help,
  30. invert,
  31. mode,
  32. blend,
  33. radius,
  34. center,
  35. pan,
  36. // TODO:
  37. // zoom,
  38. // size(window),
  39. // view(current state), // present me: what? I prosume this print current params
  40. // markers [on/off], // visual markers for center and radius
  41. // resolution?,
  42. save,
  43. commit,
  44. reset,
  45. timer,
  46. invalid
  47. };
  48. using command = simple::support::mapped_enum<commands, commands::invalid, 2>;
  49. template <> command::guts::map_type command::guts::map
  50. {{
  51. { "h"s, "help"s },
  52. { "i"s, "invert"s },
  53. { "m"s, "mode"s },
  54. { "b"s, "blend-toggle"s },
  55. { "r"s, "radius"s },
  56. { "c"s, "center"s },
  57. { "p"s, "pan"s },
  58. { "s"s, "save"s },
  59. { "cmt"s, "commit"s },
  60. { "reset"s, ""s },
  61. { "t"s, "timer-toggle"s },
  62. }};
  63. enum class modes
  64. {
  65. invert_floor,
  66. invert_round,
  67. invert_linear,
  68. outvert_floor,
  69. outvert_round,
  70. outvert_linear,
  71. invalid
  72. };
  73. using mode = simple::support::mapped_enum<modes, modes::invalid, 2>;
  74. template <> mode::guts::map_type mode::guts::map
  75. {{
  76. { "if"s, "invert-floor"s },
  77. { "ir"s, "invert-round"s },
  78. { "il"s, "invert-linear"s },
  79. { "of"s, "outvert-floor"s },
  80. { "or"s, "outvert-round"s },
  81. { "ol"s, "outvert-linear"s },
  82. }};
  83. constexpr auto inverter_func = std::array
  84. {
  85. +[](rgba_pixels src, rgba_pixels dest, int2 from, float2 to)
  86. {
  87. const float2 size{dest.size()};
  88. if(float2::zero() <= to && to < size)
  89. dest.set(src.get(from), int2(to));
  90. },
  91. +[](rgba_pixels src, rgba_pixels dest, int2 from, float2 to)
  92. {
  93. const float2 size{dest.size()};
  94. if(float2::zero() <= to && to < size)
  95. dest.set(src.get(from), round(to));
  96. },
  97. +[](rgba_pixels src, rgba_pixels dest, int2 from, float2 to)
  98. {
  99. const float2 size{dest.size()};
  100. if(float2::one(-1) < to && to < size)
  101. dest.set(src.get(from), to);
  102. },
  103. +[](rgba_pixels src, rgba_pixels dest, int2 from, float2 to)
  104. {
  105. const float2 size{src.size()};
  106. if(float2::zero() <= to && to < size)
  107. dest.set(src.get(int2(to)), from);
  108. },
  109. +[](rgba_pixels src, rgba_pixels dest, int2 from, float2 to)
  110. {
  111. const float2 size{src.size()};
  112. if(float2::zero() <= to && to < size)
  113. dest.set(src.get(round(to)), from);
  114. },
  115. +[](rgba_pixels src, rgba_pixels dest, int2 from, float2 to)
  116. {
  117. const float2 size{src.size()};
  118. if(float2::one(-1) < to && to < size)
  119. dest.set(src.get(to), from);
  120. },
  121. };
  122. int main(int argc, char** argv) try
  123. {
  124. if(argc < 2)
  125. {
  126. std::puts("Image not specified.");
  127. return -1;
  128. }
  129. initializer init;
  130. auto image = stb_load_surface(argv[1]);
  131. std::cout << image.size() << '\n';
  132. auto pixels = std::get<rgba_pixels>(image.pixels());
  133. float2 image_size(image.size());
  134. software_window win("inversion", image.size(), window::flags::borderless);
  135. auto inverted_image = surface(image);
  136. fill(inverted_image, inverted_image.format().color(0x0_rgba));
  137. auto inverted_pixels = std::get<rgba_pixels>(inverted_image.pixels());
  138. auto center = image_size/2;
  139. float radiance = (image_size.x() * image_size.y())/tau;
  140. std::string current_command;
  141. int2 offset{};
  142. mode current_mode = modes::outvert_linear;
  143. bool timer = false;
  144. do
  145. {
  146. switch(command(current_command))
  147. {
  148. case commands::help:
  149. {
  150. auto print = [](const auto& map)
  151. {
  152. for(auto&& i : map)
  153. {
  154. std::cout << '\t';
  155. for(auto&& j : i)
  156. std::cout << j << ' ';
  157. std::cout << '\n';
  158. }
  159. };
  160. std::cout << "Commands:" << '\n';
  161. print(command::guts::map);
  162. std::cout << "Modes:" << '\n';
  163. print(mode::guts::map);
  164. }
  165. break;
  166. case commands::invert:
  167. {
  168. using namespace std::chrono;
  169. auto start = steady_clock::now();
  170. fill(inverted_image, inverted_image.format().color(0x0_rgba));
  171. if(inverted_image.blend() != blend_mode::none)
  172. fill(win.surface(), win.surface().format().color(0x0_rgba));
  173. simple::geom::loop(int2::zero(), image.size(), int2::one(),
  174. [&](auto i)
  175. {
  176. auto inverted = invert(float2(i + offset),center,radiance);
  177. inverter_func[to_integer(current_mode)]
  178. (
  179. pixels, inverted_pixels,
  180. i, inverted
  181. );
  182. });
  183. blit(inverted_image, win.surface());
  184. win.update();
  185. if(timer)
  186. std::cout
  187. << duration_cast<milliseconds>(
  188. steady_clock::now() - start).count()
  189. << "ms\n";
  190. }
  191. break;
  192. case commands::mode:
  193. {
  194. std::string mode_str;
  195. if(std::cin >> mode_str)
  196. {
  197. const auto new_mode = mode(mode_str);
  198. if(new_mode != modes::invalid)
  199. current_mode = new_mode;
  200. else
  201. std::cerr << "Invalid mode!" << '\n';
  202. }
  203. else
  204. {
  205. std::cerr << "Invalid command!" << '\n';
  206. std::cin.clear();
  207. std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
  208. }
  209. }
  210. break;
  211. case commands::blend:
  212. inverted_image.blend(
  213. inverted_image.blend() != blend_mode::none
  214. ? blend_mode::none
  215. : blend_mode::alpha
  216. );
  217. break;
  218. case commands::radius:
  219. {
  220. float radius;
  221. if(std::cin >> radius)
  222. {
  223. radiance = radius * radius;
  224. }
  225. else
  226. {
  227. std::cerr << "Invalid current_command!" << '\n';
  228. std::cin.clear();
  229. std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
  230. }
  231. }
  232. break;
  233. case commands::center:
  234. if(!(std::cin >> center.x() >> center.y()))
  235. {
  236. std::cerr << "Invalid command!" << '\n';
  237. std::cin.clear();
  238. std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
  239. }
  240. break;
  241. case commands::pan:
  242. if(!(std::cin >> offset.x() >> offset.y()))
  243. {
  244. std::cerr << "Invalid command!" << '\n';
  245. std::cin.clear();
  246. std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
  247. }
  248. break;
  249. case commands::save:
  250. {
  251. std::string savename;
  252. if(std::cin >> savename)
  253. inverted_image.save((savename + ".bmp").c_str());
  254. }
  255. break;
  256. case commands::commit:
  257. blit(inverted_image, image, blend_mode::none);
  258. break;
  259. case commands::reset:
  260. if(image.blend() != blend_mode::none)
  261. fill(win.surface(), win.surface().format().color(0x0_rgba));
  262. blit(image, win.surface());
  263. win.update();
  264. break;
  265. case commands::timer:
  266. timer = !timer;
  267. break;
  268. case commands::invalid:
  269. std::cerr << "Invalid command!" << '\n';
  270. std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
  271. break;
  272. }
  273. }
  274. while(std::cin >> current_command);
  275. return 0;
  276. }
  277. catch(...)
  278. {
  279. if(errno)
  280. std::perror("ERROR");
  281. const char* sdl_error = SDL_GetError();
  282. if(*sdl_error)
  283. std::puts(sdl_error);
  284. throw;
  285. }
  286. surface stb_load_surface(const char* filename)
  287. {
  288. using namespace std::literals;
  289. int2 size;
  290. int orig_format;
  291. auto data = stbi_load(filename, &size.x(), &size.y(), &orig_format, STBI_rgb_alpha);
  292. if(data == NULL)
  293. throw std::runtime_error("Loading image failed: "s + stbi_failure_reason());
  294. return
  295. {
  296. { data, [](surface::byte* data) { stbi_image_free(data); } },
  297. size,
  298. pixel_format(pixel_format::type::rgba32)
  299. };
  300. }