main.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. /*
  2. * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include "CreateNewImageDialog.h"
  7. #include "CreateNewLayerDialog.h"
  8. #include "FilterParams.h"
  9. #include "Image.h"
  10. #include "ImageEditor.h"
  11. #include "Layer.h"
  12. #include "LayerListWidget.h"
  13. #include "LayerPropertiesWidget.h"
  14. #include "PaletteWidget.h"
  15. #include "Tool.h"
  16. #include "ToolPropertiesWidget.h"
  17. #include "ToolboxWidget.h"
  18. #include <Applications/PixelPaint/PixelPaintWindowGML.h>
  19. #include <LibCore/ArgsParser.h>
  20. #include <LibCore/File.h>
  21. #include <LibGUI/Action.h>
  22. #include <LibGUI/Application.h>
  23. #include <LibGUI/Clipboard.h>
  24. #include <LibGUI/FilePicker.h>
  25. #include <LibGUI/Icon.h>
  26. #include <LibGUI/Menubar.h>
  27. #include <LibGUI/MessageBox.h>
  28. #include <LibGUI/Statusbar.h>
  29. #include <LibGUI/Toolbar.h>
  30. #include <LibGUI/Window.h>
  31. #include <LibGfx/Bitmap.h>
  32. #include <stdio.h>
  33. #include <unistd.h>
  34. int main(int argc, char** argv)
  35. {
  36. if (pledge("stdio thread recvfd sendfd rpath unix wpath cpath", nullptr) < 0) {
  37. perror("pledge");
  38. return 1;
  39. }
  40. auto app = GUI::Application::construct(argc, argv);
  41. const char* image_file = nullptr;
  42. Core::ArgsParser args_parser;
  43. args_parser.add_positional_argument(image_file, "Image file to open", "path", Core::ArgsParser::Required::No);
  44. args_parser.parse(argc, argv);
  45. auto app_icon = GUI::Icon::default_icon("app-pixel-paint");
  46. auto window = GUI::Window::construct();
  47. window->set_title("Pixel Paint");
  48. window->resize(800, 480);
  49. window->set_icon(app_icon.bitmap_for_size(16));
  50. auto& main_widget = window->set_main_widget<GUI::Widget>();
  51. main_widget.load_from_gml(pixel_paint_window_gml);
  52. auto& toolbox = *main_widget.find_descendant_of_type_named<PixelPaint::ToolboxWidget>("toolbox");
  53. auto& image_editor = *main_widget.find_descendant_of_type_named<PixelPaint::ImageEditor>("image_editor");
  54. image_editor.set_focus(true);
  55. auto& palette_widget = *main_widget.find_descendant_of_type_named<PixelPaint::PaletteWidget>("palette_widget");
  56. palette_widget.set_image_editor(image_editor);
  57. auto& layer_list_widget = *main_widget.find_descendant_of_type_named<PixelPaint::LayerListWidget>("layer_list_widget");
  58. layer_list_widget.on_layer_select = [&](auto* layer) {
  59. image_editor.set_active_layer(layer);
  60. };
  61. auto& layer_properties_widget = *main_widget.find_descendant_of_type_named<PixelPaint::LayerPropertiesWidget>("layer_properties_widget");
  62. auto& tool_properties_widget = *main_widget.find_descendant_of_type_named<PixelPaint::ToolPropertiesWidget>("tool_properties_widget");
  63. toolbox.on_tool_selection = [&](auto* tool) {
  64. image_editor.set_active_tool(tool);
  65. tool_properties_widget.set_active_tool(tool);
  66. };
  67. auto new_image_action = GUI::Action::create(
  68. "&New Image...", { Mod_Ctrl, Key_N }, Gfx::Bitmap::load_from_file("/res/icons/16x16/new.png"), [&](auto&) {
  69. auto dialog = PixelPaint::CreateNewImageDialog::construct(window);
  70. if (dialog->exec() == GUI::Dialog::ExecOK) {
  71. auto image = PixelPaint::Image::try_create_with_size(dialog->image_size());
  72. auto bg_layer = PixelPaint::Layer::try_create_with_size(*image, image->size(), "Background");
  73. VERIFY(bg_layer);
  74. image->add_layer(*bg_layer);
  75. bg_layer->bitmap().fill(Color::White);
  76. image_editor.set_image(image);
  77. layer_list_widget.set_image(image);
  78. image_editor.set_active_layer(bg_layer);
  79. }
  80. },
  81. window);
  82. auto open_image_file = [&](auto& path) {
  83. auto image_or_error = PixelPaint::Image::try_create_from_file(path);
  84. if (image_or_error.is_error()) {
  85. GUI::MessageBox::show_error(window, String::formatted("Unable to open file: {}", path));
  86. return;
  87. }
  88. auto& image = *image_or_error.value();
  89. image_editor.set_image(image);
  90. layer_list_widget.set_image(&image);
  91. };
  92. auto open_image_action = GUI::CommonActions::make_open_action([&](auto&) {
  93. Optional<String> open_path = GUI::FilePicker::get_open_filepath(window);
  94. if (!open_path.has_value())
  95. return;
  96. open_image_file(open_path.value());
  97. });
  98. auto save_image_as_action = GUI::CommonActions::make_save_as_action([&](auto&) {
  99. if (!image_editor.image())
  100. return;
  101. auto save_path = GUI::FilePicker::get_save_filepath(window, "untitled", "pp");
  102. if (!save_path.has_value())
  103. return;
  104. auto result = image_editor.image()->write_to_file(save_path.value());
  105. if (result.is_error()) {
  106. GUI::MessageBox::show_error(window, String::formatted("Could not save {}: {}", save_path.value(), result.error()));
  107. return;
  108. }
  109. });
  110. auto menubar = GUI::Menubar::construct();
  111. auto& file_menu = menubar->add_menu("&File");
  112. file_menu.add_action(new_image_action);
  113. file_menu.add_action(open_image_action);
  114. file_menu.add_action(save_image_as_action);
  115. auto& export_submenu = file_menu.add_submenu("&Export");
  116. export_submenu.add_action(
  117. GUI::Action::create(
  118. "As &BMP", [&](auto&) {
  119. if (!image_editor.image())
  120. return;
  121. Optional<String> save_path = GUI::FilePicker::get_save_filepath(window, "untitled", "bmp");
  122. if (!save_path.has_value())
  123. return;
  124. image_editor.image()->export_bmp(save_path.value());
  125. },
  126. window));
  127. export_submenu.add_action(
  128. GUI::Action::create(
  129. "As &PNG", [&](auto&) {
  130. if (!image_editor.image())
  131. return;
  132. Optional<String> save_path = GUI::FilePicker::get_save_filepath(window, "untitled", "png");
  133. if (!save_path.has_value())
  134. return;
  135. image_editor.image()->export_png(save_path.value());
  136. },
  137. window));
  138. file_menu.add_separator();
  139. file_menu.add_action(GUI::CommonActions::make_quit_action([](auto&) {
  140. GUI::Application::the()->quit();
  141. }));
  142. auto& edit_menu = menubar->add_menu("&Edit");
  143. auto copy_action = GUI::CommonActions::make_copy_action([&](auto&) {
  144. VERIFY(image_editor.image());
  145. if (!image_editor.active_layer()) {
  146. dbgln("Cannot copy with no active layer selected");
  147. return;
  148. }
  149. auto bitmap = image_editor.active_layer()->try_copy_bitmap(image_editor.selection());
  150. if (!bitmap) {
  151. dbgln("try_copy() from Layer failed");
  152. return;
  153. }
  154. GUI::Clipboard::the().set_bitmap(*bitmap);
  155. });
  156. auto paste_action = GUI::CommonActions::make_paste_action([&](auto&) {
  157. VERIFY(image_editor.image());
  158. auto bitmap = GUI::Clipboard::the().bitmap();
  159. if (!bitmap)
  160. return;
  161. auto layer = PixelPaint::Layer::try_create_with_bitmap(*image_editor.image(), *bitmap, "Pasted layer");
  162. VERIFY(layer);
  163. image_editor.image()->add_layer(*layer);
  164. image_editor.set_active_layer(layer);
  165. image_editor.selection().clear();
  166. });
  167. GUI::Clipboard::the().on_change = [&](auto& mime_type) {
  168. paste_action->set_enabled(mime_type == "image/x-serenityos");
  169. };
  170. paste_action->set_enabled(GUI::Clipboard::the().mime_type() == "image/x-serenityos");
  171. edit_menu.add_action(copy_action);
  172. edit_menu.add_action(paste_action);
  173. auto undo_action = GUI::CommonActions::make_undo_action([&](auto&) {
  174. VERIFY(image_editor.image());
  175. image_editor.undo();
  176. });
  177. edit_menu.add_action(undo_action);
  178. auto redo_action = GUI::CommonActions::make_redo_action([&](auto&) {
  179. VERIFY(image_editor.image());
  180. image_editor.redo();
  181. });
  182. edit_menu.add_action(redo_action);
  183. edit_menu.add_separator();
  184. edit_menu.add_action(GUI::CommonActions::make_select_all_action([&](auto&) {
  185. VERIFY(image_editor.image());
  186. if (!image_editor.active_layer())
  187. return;
  188. image_editor.selection().set(image_editor.active_layer()->relative_rect());
  189. }));
  190. edit_menu.add_action(GUI::Action::create(
  191. "Clear &Selection", { Mod_Ctrl | Mod_Shift, Key_A }, [&](auto&) {
  192. image_editor.selection().clear();
  193. },
  194. window));
  195. edit_menu.add_separator();
  196. edit_menu.add_action(GUI::Action::create(
  197. "&Swap Colors", { Mod_None, Key_X }, [&](auto&) {
  198. auto old_primary_color = image_editor.primary_color();
  199. image_editor.set_primary_color(image_editor.secondary_color());
  200. image_editor.set_secondary_color(old_primary_color);
  201. },
  202. window));
  203. edit_menu.add_action(GUI::Action::create(
  204. "&Default Colors", { Mod_None, Key_D }, [&](auto&) {
  205. image_editor.set_primary_color(Color::Black);
  206. image_editor.set_secondary_color(Color::White);
  207. },
  208. window));
  209. auto& view_menu = menubar->add_menu("&View");
  210. auto zoom_in_action = GUI::CommonActions::make_zoom_in_action(
  211. [&](auto&) {
  212. image_editor.scale_by(0.1f);
  213. },
  214. window);
  215. auto zoom_out_action = GUI::CommonActions::make_zoom_out_action(
  216. [&](auto&) {
  217. image_editor.scale_by(-0.1f);
  218. },
  219. window);
  220. auto reset_zoom_action = GUI::CommonActions::make_reset_zoom_action(
  221. [&](auto&) {
  222. image_editor.reset_scale_and_position();
  223. },
  224. window);
  225. view_menu.add_action(zoom_in_action);
  226. view_menu.add_action(zoom_out_action);
  227. view_menu.add_action(reset_zoom_action);
  228. auto& tool_menu = menubar->add_menu("&Tool");
  229. toolbox.for_each_tool([&](auto& tool) {
  230. if (tool.action())
  231. tool_menu.add_action(*tool.action());
  232. return IterationDecision::Continue;
  233. });
  234. auto& layer_menu = menubar->add_menu("&Layer");
  235. layer_menu.add_action(GUI::Action::create(
  236. "New &Layer...", { Mod_Ctrl | Mod_Shift, Key_N }, [&](auto&) {
  237. auto dialog = PixelPaint::CreateNewLayerDialog::construct(image_editor.image()->size(), window);
  238. if (dialog->exec() == GUI::Dialog::ExecOK) {
  239. auto layer = PixelPaint::Layer::try_create_with_size(*image_editor.image(), dialog->layer_size(), dialog->layer_name());
  240. if (!layer) {
  241. GUI::MessageBox::show_error(window, String::formatted("Unable to create layer with size {}", dialog->size().to_string()));
  242. return;
  243. }
  244. image_editor.image()->add_layer(layer.release_nonnull());
  245. image_editor.layers_did_change();
  246. }
  247. },
  248. window));
  249. layer_menu.add_separator();
  250. layer_menu.add_action(GUI::Action::create(
  251. "Select &Previous Layer", { 0, Key_PageUp }, [&](auto&) {
  252. layer_list_widget.move_selection(1);
  253. },
  254. window));
  255. layer_menu.add_action(GUI::Action::create(
  256. "Select &Next Layer", { 0, Key_PageDown }, [&](auto&) {
  257. layer_list_widget.move_selection(-1);
  258. },
  259. window));
  260. layer_menu.add_action(GUI::Action::create(
  261. "Select &Top Layer", { 0, Key_Home }, [&](auto&) {
  262. layer_list_widget.select_top_layer();
  263. },
  264. window));
  265. layer_menu.add_action(GUI::Action::create(
  266. "Select &Bottom Layer", { 0, Key_End }, [&](auto&) {
  267. layer_list_widget.select_bottom_layer();
  268. },
  269. window));
  270. layer_menu.add_separator();
  271. layer_menu.add_action(GUI::Action::create(
  272. "Move Active Layer &Up", { Mod_Ctrl, Key_PageUp }, [&](auto&) {
  273. auto active_layer = image_editor.active_layer();
  274. if (!active_layer)
  275. return;
  276. image_editor.image()->move_layer_up(*active_layer);
  277. },
  278. window));
  279. layer_menu.add_action(GUI::Action::create(
  280. "Move Active Layer &Down", { Mod_Ctrl, Key_PageDown }, [&](auto&) {
  281. auto active_layer = image_editor.active_layer();
  282. if (!active_layer)
  283. return;
  284. image_editor.image()->move_layer_down(*active_layer);
  285. },
  286. window));
  287. layer_menu.add_separator();
  288. layer_menu.add_action(GUI::Action::create(
  289. "&Remove Active Layer", { Mod_Ctrl, Key_D }, [&](auto&) {
  290. auto active_layer = image_editor.active_layer();
  291. if (!active_layer)
  292. return;
  293. image_editor.image()->remove_layer(*active_layer);
  294. image_editor.set_active_layer(nullptr);
  295. },
  296. window));
  297. auto& filter_menu = menubar->add_menu("&Filter");
  298. auto& spatial_filters_menu = filter_menu.add_submenu("&Spatial");
  299. auto& edge_detect_submenu = spatial_filters_menu.add_submenu("&Edge Detect");
  300. edge_detect_submenu.add_action(GUI::Action::create("Laplacian (&Cardinal)", [&](auto&) {
  301. if (auto* layer = image_editor.active_layer()) {
  302. Gfx::LaplacianFilter filter;
  303. if (auto parameters = PixelPaint::FilterParameters<Gfx::LaplacianFilter>::get(false)) {
  304. filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
  305. image_editor.did_complete_action();
  306. }
  307. }
  308. }));
  309. edge_detect_submenu.add_action(GUI::Action::create("Laplacian (&Diagonal)", [&](auto&) {
  310. if (auto* layer = image_editor.active_layer()) {
  311. Gfx::LaplacianFilter filter;
  312. if (auto parameters = PixelPaint::FilterParameters<Gfx::LaplacianFilter>::get(true)) {
  313. filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
  314. image_editor.did_complete_action();
  315. }
  316. }
  317. }));
  318. auto& blur_submenu = spatial_filters_menu.add_submenu("&Blur and Sharpen");
  319. blur_submenu.add_action(GUI::Action::create("&Gaussian Blur (3x3)", [&](auto&) {
  320. if (auto* layer = image_editor.active_layer()) {
  321. Gfx::SpatialGaussianBlurFilter<3> filter;
  322. if (auto parameters = PixelPaint::FilterParameters<Gfx::SpatialGaussianBlurFilter<3>>::get()) {
  323. filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
  324. image_editor.did_complete_action();
  325. }
  326. }
  327. }));
  328. blur_submenu.add_action(GUI::Action::create("G&aussian Blur (5x5)", [&](auto&) {
  329. if (auto* layer = image_editor.active_layer()) {
  330. Gfx::SpatialGaussianBlurFilter<5> filter;
  331. if (auto parameters = PixelPaint::FilterParameters<Gfx::SpatialGaussianBlurFilter<5>>::get()) {
  332. filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
  333. image_editor.did_complete_action();
  334. }
  335. }
  336. }));
  337. blur_submenu.add_action(GUI::Action::create("&Box Blur (3x3)", [&](auto&) {
  338. if (auto* layer = image_editor.active_layer()) {
  339. Gfx::BoxBlurFilter<3> filter;
  340. if (auto parameters = PixelPaint::FilterParameters<Gfx::BoxBlurFilter<3>>::get()) {
  341. filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
  342. image_editor.did_complete_action();
  343. }
  344. }
  345. }));
  346. blur_submenu.add_action(GUI::Action::create("B&ox Blur (5x5)", [&](auto&) {
  347. if (auto* layer = image_editor.active_layer()) {
  348. Gfx::BoxBlurFilter<5> filter;
  349. if (auto parameters = PixelPaint::FilterParameters<Gfx::BoxBlurFilter<5>>::get()) {
  350. filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
  351. image_editor.did_complete_action();
  352. }
  353. }
  354. }));
  355. blur_submenu.add_action(GUI::Action::create("&Sharpen", [&](auto&) {
  356. if (auto* layer = image_editor.active_layer()) {
  357. Gfx::SharpenFilter filter;
  358. if (auto parameters = PixelPaint::FilterParameters<Gfx::SharpenFilter>::get()) {
  359. filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
  360. image_editor.did_complete_action();
  361. }
  362. }
  363. }));
  364. spatial_filters_menu.add_separator();
  365. spatial_filters_menu.add_action(GUI::Action::create("Generic 5x5 &Convolution", [&](auto&) {
  366. if (auto* layer = image_editor.active_layer()) {
  367. Gfx::GenericConvolutionFilter<5> filter;
  368. if (auto parameters = PixelPaint::FilterParameters<Gfx::GenericConvolutionFilter<5>>::get(window)) {
  369. filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
  370. image_editor.did_complete_action();
  371. }
  372. }
  373. }));
  374. auto& help_menu = menubar->add_menu("&Help");
  375. help_menu.add_action(GUI::CommonActions::make_about_action("Pixel Paint", app_icon, window));
  376. window->set_menubar(move(menubar));
  377. auto& toolbar = *main_widget.find_descendant_of_type_named<GUI::Toolbar>("toolbar");
  378. toolbar.add_action(new_image_action);
  379. toolbar.add_action(open_image_action);
  380. toolbar.add_action(save_image_as_action);
  381. toolbar.add_separator();
  382. toolbar.add_action(copy_action);
  383. toolbar.add_action(paste_action);
  384. toolbar.add_action(undo_action);
  385. toolbar.add_action(redo_action);
  386. toolbar.add_separator();
  387. toolbar.add_action(zoom_in_action);
  388. toolbar.add_action(zoom_out_action);
  389. toolbar.add_action(reset_zoom_action);
  390. image_editor.on_active_layer_change = [&](auto* layer) {
  391. layer_list_widget.set_selected_layer(layer);
  392. layer_properties_widget.set_layer(layer);
  393. };
  394. auto image_file_real_path = Core::File::real_path_for(image_file);
  395. if (Core::File::exists(image_file_real_path)) {
  396. open_image_file(image_file_real_path);
  397. } else {
  398. auto image = PixelPaint::Image::try_create_with_size({ 480, 360 });
  399. auto bg_layer = PixelPaint::Layer::try_create_with_size(*image, image->size(), "Background");
  400. VERIFY(bg_layer);
  401. image->add_layer(*bg_layer);
  402. bg_layer->bitmap().fill(Color::White);
  403. layer_list_widget.set_image(image);
  404. image_editor.set_image(image);
  405. image_editor.set_active_layer(bg_layer);
  406. }
  407. auto& statusbar = *main_widget.find_descendant_of_type_named<GUI::Statusbar>("statusbar");
  408. app->on_action_enter = [&statusbar](GUI::Action& action) {
  409. auto text = action.status_tip();
  410. if (text.is_empty())
  411. text = Gfx::parse_ampersand_string(action.text());
  412. statusbar.set_override_text(move(text));
  413. };
  414. app->on_action_leave = [&statusbar](GUI::Action&) {
  415. statusbar.set_override_text({});
  416. };
  417. window->show();
  418. return app->exec();
  419. }