bonus_04_inversion.cpp 7.4 KB

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