main.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. /*
  2. * Copyright (c) 2018-2020, 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 <LibCore/ArgsParser.h>
  19. #include <LibCore/File.h>
  20. #include <LibGUI/Action.h>
  21. #include <LibGUI/Application.h>
  22. #include <LibGUI/BoxLayout.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/Window.h>
  29. #include <LibGfx/Bitmap.h>
  30. #include <LibGfx/Matrix4x4.h>
  31. #include <stdio.h>
  32. #include <unistd.h>
  33. int main(int argc, char** argv)
  34. {
  35. if (pledge("stdio thread recvfd sendfd rpath unix wpath cpath", nullptr) < 0) {
  36. perror("pledge");
  37. return 1;
  38. }
  39. auto app = GUI::Application::construct(argc, argv);
  40. if (pledge("stdio thread recvfd sendfd rpath wpath cpath", nullptr) < 0) {
  41. perror("pledge");
  42. return 1;
  43. }
  44. const char* image_file = nullptr;
  45. Core::ArgsParser args_parser;
  46. args_parser.add_positional_argument(image_file, "PixelPaint image file (*.pp) to open", "path", Core::ArgsParser::Required::No);
  47. args_parser.parse(argc, argv);
  48. auto app_icon = GUI::Icon::default_icon("app-pixel-paint");
  49. auto window = GUI::Window::construct();
  50. window->set_title("PixelPaint");
  51. window->resize(950, 570);
  52. window->set_icon(app_icon.bitmap_for_size(16));
  53. auto& horizontal_container = window->set_main_widget<GUI::Widget>();
  54. horizontal_container.set_layout<GUI::HorizontalBoxLayout>();
  55. horizontal_container.layout()->set_spacing(0);
  56. auto& toolbox = horizontal_container.add<PixelPaint::ToolboxWidget>();
  57. auto& vertical_container = horizontal_container.add<GUI::Widget>();
  58. vertical_container.set_layout<GUI::VerticalBoxLayout>();
  59. vertical_container.layout()->set_spacing(0);
  60. auto& image_editor = vertical_container.add<PixelPaint::ImageEditor>();
  61. image_editor.set_focus(true);
  62. vertical_container.add<PixelPaint::PaletteWidget>(image_editor);
  63. auto& right_panel = horizontal_container.add<GUI::Widget>();
  64. right_panel.set_fill_with_background_color(true);
  65. right_panel.set_fixed_width(230);
  66. right_panel.set_layout<GUI::VerticalBoxLayout>();
  67. auto& layer_list_widget = right_panel.add<PixelPaint::LayerListWidget>();
  68. layer_list_widget.on_layer_select = [&](auto* layer) {
  69. image_editor.set_active_layer(layer);
  70. };
  71. auto& layer_properties_widget = right_panel.add<PixelPaint::LayerPropertiesWidget>();
  72. auto& tool_properties_widget = right_panel.add<PixelPaint::ToolPropertiesWidget>();
  73. toolbox.on_tool_selection = [&](auto* tool) {
  74. image_editor.set_active_tool(tool);
  75. tool_properties_widget.set_active_tool(tool);
  76. };
  77. window->show();
  78. auto menubar = GUI::Menubar::construct();
  79. auto& file_menu = menubar->add_menu("&File");
  80. auto open_image_file = [&](auto& path) {
  81. auto image = PixelPaint::Image::create_from_file(path);
  82. if (!image) {
  83. GUI::MessageBox::show_error(window, String::formatted("Invalid image file: {}", path));
  84. return;
  85. }
  86. image_editor.set_image(image);
  87. layer_list_widget.set_image(image);
  88. };
  89. file_menu.add_action(
  90. GUI::Action::create(
  91. "&New Image...", { Mod_Ctrl, Key_N }, Gfx::Bitmap::load_from_file("/res/icons/16x16/new.png"), [&](auto&) {
  92. auto dialog = PixelPaint::CreateNewImageDialog::construct(window);
  93. if (dialog->exec() == GUI::Dialog::ExecOK) {
  94. auto image = PixelPaint::Image::create_with_size(dialog->image_size());
  95. auto bg_layer = PixelPaint::Layer::create_with_size(*image, image->size(), "Background");
  96. image->add_layer(*bg_layer);
  97. bg_layer->bitmap().fill(Color::White);
  98. image_editor.set_image(image);
  99. layer_list_widget.set_image(image);
  100. image_editor.set_active_layer(bg_layer);
  101. }
  102. },
  103. window));
  104. file_menu.add_action(GUI::CommonActions::make_open_action([&](auto&) {
  105. Optional<String> open_path = GUI::FilePicker::get_open_filepath(window);
  106. if (!open_path.has_value())
  107. return;
  108. open_image_file(open_path.value());
  109. }));
  110. file_menu.add_action(GUI::CommonActions::make_save_as_action([&](auto&) {
  111. if (!image_editor.image())
  112. return;
  113. Optional<String> save_path = GUI::FilePicker::get_save_filepath(window, "untitled", "pp");
  114. if (!save_path.has_value())
  115. return;
  116. image_editor.image()->save(save_path.value());
  117. }));
  118. auto& export_submenu = file_menu.add_submenu("&Export");
  119. export_submenu.add_action(
  120. GUI::Action::create(
  121. "As &BMP", [&](auto&) {
  122. if (!image_editor.image())
  123. return;
  124. Optional<String> save_path = GUI::FilePicker::get_save_filepath(window, "untitled", "bmp");
  125. if (!save_path.has_value())
  126. return;
  127. image_editor.image()->export_bmp(save_path.value());
  128. },
  129. window));
  130. export_submenu.add_action(
  131. GUI::Action::create(
  132. "As &PNG", [&](auto&) {
  133. if (!image_editor.image())
  134. return;
  135. Optional<String> save_path = GUI::FilePicker::get_save_filepath(window, "untitled", "png");
  136. if (!save_path.has_value())
  137. return;
  138. image_editor.image()->export_png(save_path.value());
  139. },
  140. window));
  141. file_menu.add_separator();
  142. file_menu.add_action(GUI::CommonActions::make_quit_action([](auto&) {
  143. GUI::Application::the()->quit();
  144. }));
  145. auto& edit_menu = menubar->add_menu("&Edit");
  146. auto paste_action = GUI::CommonActions::make_paste_action([&](auto&) {
  147. VERIFY(image_editor.image());
  148. auto bitmap = GUI::Clipboard::the().bitmap();
  149. if (!bitmap)
  150. return;
  151. auto layer = PixelPaint::Layer::create_with_bitmap(*image_editor.image(), *bitmap, "Pasted layer");
  152. image_editor.image()->add_layer(layer.release_nonnull());
  153. });
  154. GUI::Clipboard::the().on_change = [&](auto& mime_type) {
  155. paste_action->set_enabled(mime_type == "image/x-serenityos");
  156. };
  157. paste_action->set_enabled(GUI::Clipboard::the().mime_type() == "image/x-serenityos");
  158. edit_menu.add_action(paste_action);
  159. auto undo_action = GUI::CommonActions::make_undo_action([&](auto&) {
  160. VERIFY(image_editor.image());
  161. image_editor.undo();
  162. });
  163. edit_menu.add_action(undo_action);
  164. auto redo_action = GUI::CommonActions::make_redo_action([&](auto&) {
  165. VERIFY(image_editor.image());
  166. image_editor.redo();
  167. });
  168. edit_menu.add_action(redo_action);
  169. auto& view_menu = menubar->add_menu("&View");
  170. auto zoom_in_action = GUI::CommonActions::make_zoom_in_action(
  171. [&](auto&) {
  172. image_editor.scale_by(0.1f);
  173. },
  174. window);
  175. auto zoom_out_action = GUI::CommonActions::make_zoom_out_action(
  176. [&](auto&) {
  177. image_editor.scale_by(-0.1f);
  178. },
  179. window);
  180. auto reset_zoom_action = GUI::CommonActions::make_reset_zoom_action(
  181. [&](auto&) {
  182. image_editor.reset_scale_and_position();
  183. },
  184. window);
  185. view_menu.add_action(zoom_in_action);
  186. view_menu.add_action(zoom_out_action);
  187. view_menu.add_action(reset_zoom_action);
  188. auto& tool_menu = menubar->add_menu("&Tool");
  189. toolbox.for_each_tool([&](auto& tool) {
  190. if (tool.action())
  191. tool_menu.add_action(*tool.action());
  192. return IterationDecision::Continue;
  193. });
  194. auto& layer_menu = menubar->add_menu("&Layer");
  195. layer_menu.add_action(GUI::Action::create(
  196. "&New Layer...", { Mod_Ctrl | Mod_Shift, Key_N }, [&](auto&) {
  197. auto dialog = PixelPaint::CreateNewLayerDialog::construct(image_editor.image()->size(), window);
  198. if (dialog->exec() == GUI::Dialog::ExecOK) {
  199. auto layer = PixelPaint::Layer::create_with_size(*image_editor.image(), dialog->layer_size(), dialog->layer_name());
  200. if (!layer) {
  201. GUI::MessageBox::show_error(window, String::formatted("Unable to create layer with size {}", dialog->size().to_string()));
  202. return;
  203. }
  204. image_editor.image()->add_layer(layer.release_nonnull());
  205. image_editor.layers_did_change();
  206. }
  207. },
  208. window));
  209. layer_menu.add_separator();
  210. layer_menu.add_action(GUI::Action::create(
  211. "Select &Previous Layer", { 0, Key_PageUp }, [&](auto&) {
  212. layer_list_widget.move_selection(1);
  213. },
  214. window));
  215. layer_menu.add_action(GUI::Action::create(
  216. "Select &Next Layer", { 0, Key_PageDown }, [&](auto&) {
  217. layer_list_widget.move_selection(-1);
  218. },
  219. window));
  220. layer_menu.add_action(GUI::Action::create(
  221. "Select &Top Layer", { 0, Key_Home }, [&](auto&) {
  222. layer_list_widget.select_top_layer();
  223. },
  224. window));
  225. layer_menu.add_action(GUI::Action::create(
  226. "Select &Bottom Layer", { 0, Key_End }, [&](auto&) {
  227. layer_list_widget.select_bottom_layer();
  228. },
  229. window));
  230. layer_menu.add_separator();
  231. layer_menu.add_action(GUI::Action::create(
  232. "Move Active Layer &Up", { Mod_Ctrl, Key_PageUp }, [&](auto&) {
  233. auto active_layer = image_editor.active_layer();
  234. if (!active_layer)
  235. return;
  236. image_editor.image()->move_layer_up(*active_layer);
  237. },
  238. window));
  239. layer_menu.add_action(GUI::Action::create(
  240. "Move Active Layer &Down", { Mod_Ctrl, Key_PageDown }, [&](auto&) {
  241. auto active_layer = image_editor.active_layer();
  242. if (!active_layer)
  243. return;
  244. image_editor.image()->move_layer_down(*active_layer);
  245. },
  246. window));
  247. layer_menu.add_separator();
  248. layer_menu.add_action(GUI::Action::create(
  249. "&Remove Active Layer", { Mod_Ctrl, Key_D }, [&](auto&) {
  250. auto active_layer = image_editor.active_layer();
  251. if (!active_layer)
  252. return;
  253. image_editor.image()->remove_layer(*active_layer);
  254. image_editor.set_active_layer(nullptr);
  255. },
  256. window));
  257. auto& filter_menu = menubar->add_menu("&Filter");
  258. auto& spatial_filters_menu = filter_menu.add_submenu("&Spatial");
  259. auto& edge_detect_submenu = spatial_filters_menu.add_submenu("&Edge Detect");
  260. edge_detect_submenu.add_action(GUI::Action::create("Laplacian (&Cardinal)", [&](auto&) {
  261. if (auto* layer = image_editor.active_layer()) {
  262. Gfx::LaplacianFilter filter;
  263. if (auto parameters = PixelPaint::FilterParameters<Gfx::LaplacianFilter>::get(false)) {
  264. filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
  265. image_editor.did_complete_action();
  266. }
  267. }
  268. }));
  269. edge_detect_submenu.add_action(GUI::Action::create("Laplacian (&Diagonal)", [&](auto&) {
  270. if (auto* layer = image_editor.active_layer()) {
  271. Gfx::LaplacianFilter filter;
  272. if (auto parameters = PixelPaint::FilterParameters<Gfx::LaplacianFilter>::get(true)) {
  273. filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
  274. image_editor.did_complete_action();
  275. }
  276. }
  277. }));
  278. auto& blur_submenu = spatial_filters_menu.add_submenu("&Blur and Sharpen");
  279. blur_submenu.add_action(GUI::Action::create("&Gaussian Blur (3x3)", [&](auto&) {
  280. if (auto* layer = image_editor.active_layer()) {
  281. Gfx::SpatialGaussianBlurFilter<3> filter;
  282. if (auto parameters = PixelPaint::FilterParameters<Gfx::SpatialGaussianBlurFilter<3>>::get()) {
  283. filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
  284. image_editor.did_complete_action();
  285. }
  286. }
  287. }));
  288. blur_submenu.add_action(GUI::Action::create("&Gaussian Blur (5x5)", [&](auto&) {
  289. if (auto* layer = image_editor.active_layer()) {
  290. Gfx::SpatialGaussianBlurFilter<5> filter;
  291. if (auto parameters = PixelPaint::FilterParameters<Gfx::SpatialGaussianBlurFilter<5>>::get()) {
  292. filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
  293. image_editor.did_complete_action();
  294. }
  295. }
  296. }));
  297. blur_submenu.add_action(GUI::Action::create("&Box Blur (3x3)", [&](auto&) {
  298. if (auto* layer = image_editor.active_layer()) {
  299. Gfx::BoxBlurFilter<3> filter;
  300. if (auto parameters = PixelPaint::FilterParameters<Gfx::BoxBlurFilter<3>>::get()) {
  301. filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
  302. image_editor.did_complete_action();
  303. }
  304. }
  305. }));
  306. blur_submenu.add_action(GUI::Action::create("&Box Blur (5x5)", [&](auto&) {
  307. if (auto* layer = image_editor.active_layer()) {
  308. Gfx::BoxBlurFilter<5> filter;
  309. if (auto parameters = PixelPaint::FilterParameters<Gfx::BoxBlurFilter<5>>::get()) {
  310. filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
  311. image_editor.did_complete_action();
  312. }
  313. }
  314. }));
  315. blur_submenu.add_action(GUI::Action::create("&Sharpen", [&](auto&) {
  316. if (auto* layer = image_editor.active_layer()) {
  317. Gfx::SharpenFilter filter;
  318. if (auto parameters = PixelPaint::FilterParameters<Gfx::SharpenFilter>::get()) {
  319. filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
  320. image_editor.did_complete_action();
  321. }
  322. }
  323. }));
  324. spatial_filters_menu.add_separator();
  325. spatial_filters_menu.add_action(GUI::Action::create("Generic 5x5 &Convolution", [&](auto&) {
  326. if (auto* layer = image_editor.active_layer()) {
  327. Gfx::GenericConvolutionFilter<5> filter;
  328. if (auto parameters = PixelPaint::FilterParameters<Gfx::GenericConvolutionFilter<5>>::get(window)) {
  329. filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
  330. image_editor.did_complete_action();
  331. }
  332. }
  333. }));
  334. auto& help_menu = menubar->add_menu("&Help");
  335. help_menu.add_action(GUI::CommonActions::make_about_action("PixelPaint", app_icon, window));
  336. window->set_menubar(move(menubar));
  337. image_editor.on_active_layer_change = [&](auto* layer) {
  338. layer_list_widget.set_selected_layer(layer);
  339. layer_properties_widget.set_layer(layer);
  340. };
  341. auto image_file_real_path = Core::File::real_path_for(image_file);
  342. if (Core::File::exists(image_file_real_path)) {
  343. open_image_file(image_file_real_path);
  344. } else {
  345. auto image = PixelPaint::Image::create_with_size({ 640, 480 });
  346. auto bg_layer = PixelPaint::Layer::create_with_size(*image, { 640, 480 }, "Background");
  347. image->add_layer(*bg_layer);
  348. bg_layer->bitmap().fill(Color::White);
  349. auto fg_layer1 = PixelPaint::Layer::create_with_size(*image, { 200, 200 }, "FG Layer 1");
  350. fg_layer1->set_location({ 50, 50 });
  351. image->add_layer(*fg_layer1);
  352. fg_layer1->bitmap().fill(Color::Yellow);
  353. auto fg_layer2 = PixelPaint::Layer::create_with_size(*image, { 100, 100 }, "FG Layer 2");
  354. fg_layer2->set_location({ 300, 300 });
  355. image->add_layer(*fg_layer2);
  356. fg_layer2->bitmap().fill(Color::Blue);
  357. layer_list_widget.set_image(image);
  358. image_editor.set_image(image);
  359. image_editor.set_active_layer(bg_layer);
  360. }
  361. return app->exec();
  362. }