From 5f15fb17d9d80a8c8b042b9860516fb7e72547bd Mon Sep 17 00:00:00 2001 From: BenJilks Date: Mon, 21 Sep 2020 20:45:53 +0100 Subject: [PATCH] PixelPaint: Basic brush tool This patch adds a very basic implementation of the classic brush tool. It features a wide brush with a falloff around the edges. --- Applications/PixelPaint/BrushTool.cpp | 114 ++++++++++++++++++++++ Applications/PixelPaint/BrushTool.h | 50 ++++++++++ Applications/PixelPaint/CMakeLists.txt | 1 + Applications/PixelPaint/ToolboxWidget.cpp | 2 + Base/res/icons/pixelpaint/brush.png | Bin 0 -> 980 bytes 5 files changed, 167 insertions(+) create mode 100644 Applications/PixelPaint/BrushTool.cpp create mode 100644 Applications/PixelPaint/BrushTool.h create mode 100644 Base/res/icons/pixelpaint/brush.png diff --git a/Applications/PixelPaint/BrushTool.cpp b/Applications/PixelPaint/BrushTool.cpp new file mode 100644 index 0000000000..6e89eab88b --- /dev/null +++ b/Applications/PixelPaint/BrushTool.cpp @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2020, Ben Jilks + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "BrushTool.h" +#include "ImageEditor.h" +#include "Layer.h" +#include +#include +#include +#include + +namespace PixelPaint { + +BrushTool::BrushTool() +{ +} + +BrushTool::~BrushTool() +{ +} + +void BrushTool::on_mousedown(Layer&, GUI::MouseEvent& event, GUI::MouseEvent&) +{ + if (event.button() != GUI::MouseButton::Left && event.button() != GUI::MouseButton::Right) + return; + + m_last_position = event.position(); +} + +void BrushTool::on_mousemove(Layer& layer, GUI::MouseEvent& event, GUI::MouseEvent&) +{ + if (!(event.buttons() & GUI::MouseButton::Left || event.buttons() & GUI::MouseButton::Right)) + return; + + draw_line(layer.bitmap(), m_editor->color_for(event), m_last_position, event.position()); + layer.did_modify_bitmap(*m_editor->image()); + m_last_position = event.position(); +} + +void BrushTool::draw_point(Gfx::Bitmap& bitmap, const Gfx::Color& color, const Gfx::IntPoint& point) +{ + for (int y = point.y() - m_size; y < point.y() + m_size; y++) { + for (int x = point.x() - m_size; x < point.x() + m_size; x++) { + auto distance = point.distance_from({ x, y }); + if (x < 0 || x >= bitmap.width() || y < 0 || y >= bitmap.height()) + continue; + if (distance >= m_size) + continue; + + auto falloff = (1.0 - (distance / (float)m_size)) * 0.2; + auto pixel_color = color; + pixel_color.set_alpha(falloff * 255); + bitmap.set_pixel(x, y, bitmap.get_pixel(x, y).blend(pixel_color)); + } + } +} + +void BrushTool::draw_line(Gfx::Bitmap& bitmap, const Gfx::Color& color, const Gfx::IntPoint& start, const Gfx::IntPoint& end) +{ + int length_x = end.x() - start.x(); + int length_y = end.y() - start.y(); + float y_step = length_y == 0 ? 0 : (float)(length_y) / (float)(length_x); + if (y_step > abs(length_y)) + y_step = abs(length_y); + if (y_step < -abs(length_y)) + y_step = -abs(length_y); + if (y_step == 0 && start.x() == end.x()) + return; + + int start_x = start.x(); + int end_x = end.x(); + int start_y = start.y(); + int end_y = end.y(); + if (start_x > end_x) { + swap(start_x, end_x); + swap(start_y, end_y); + } + + float y = start_y; + for (int x = start_x; x <= end_x; x++) { + int start_step_y = y; + int end_step_y = y + y_step; + if (start_step_y > end_step_y) + swap(start_step_y, end_step_y); + for (int i = start_step_y; i <= end_step_y; i++) + draw_point(bitmap, color, { x, i }); + y += y_step; + } +} + +} diff --git a/Applications/PixelPaint/BrushTool.h b/Applications/PixelPaint/BrushTool.h new file mode 100644 index 0000000000..68d25385d2 --- /dev/null +++ b/Applications/PixelPaint/BrushTool.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020, Ben Jilks + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "Tool.h" + +namespace PixelPaint { + +class BrushTool final : public Tool { +public: + BrushTool(); + virtual ~BrushTool() override; + + virtual void on_mousedown(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& image_event) override; + virtual void on_mousemove(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& image_event) override; + +private: + int m_size { 10 }; + Gfx::IntPoint m_last_position; + + virtual const char* class_name() const override { return "BrushTool"; } + void draw_line(Gfx::Bitmap& bitmap, const Gfx::Color& color, const Gfx::IntPoint& start, const Gfx::IntPoint& end); + void draw_point(Gfx::Bitmap& bitmap, const Gfx::Color& color, const Gfx::IntPoint& point); +}; + +} diff --git a/Applications/PixelPaint/CMakeLists.txt b/Applications/PixelPaint/CMakeLists.txt index 52c55e48b0..ca317ae46f 100644 --- a/Applications/PixelPaint/CMakeLists.txt +++ b/Applications/PixelPaint/CMakeLists.txt @@ -1,4 +1,5 @@ set(SOURCES + BrushTool.cpp BucketTool.cpp CreateNewLayerDialog.cpp EllipseTool.cpp diff --git a/Applications/PixelPaint/ToolboxWidget.cpp b/Applications/PixelPaint/ToolboxWidget.cpp index a57c2c3938..d497506df3 100644 --- a/Applications/PixelPaint/ToolboxWidget.cpp +++ b/Applications/PixelPaint/ToolboxWidget.cpp @@ -25,6 +25,7 @@ */ #include "ToolboxWidget.h" +#include "BrushTool.h" #include "BucketTool.h" #include "EllipseTool.h" #include "EraseTool.h" @@ -127,6 +128,7 @@ void ToolboxWidget::setup_tools() add_tool("Move", "move", { 0, Key_M }, make()); add_tool("Pen", "pen", { 0, Key_N }, make()); + add_tool("Brush", "brush", { 0, Key_P }, make()); add_tool("Bucket Fill", "bucket", { Mod_Shift, Key_B }, make()); add_tool("Spray", "spray", { Mod_Shift, Key_S }, make()); add_tool("Color Picker", "picker", { 0, Key_O }, make()); diff --git a/Base/res/icons/pixelpaint/brush.png b/Base/res/icons/pixelpaint/brush.png new file mode 100644 index 0000000000000000000000000000000000000000..5b09f4eff8f1e914e06e6590c486aa40101c44e1 GIT binary patch literal 980 zcmeAS@N?(olHy`uVBq!ia0y~yV2}b~4mJh`hLv7E=NK3mTQZ%U13aCb6$*;-(=u~X z85lGs)=sqbIP4&EG(LD~losDwg+~(>T15(o7GBX3S?IJvG^*5K=NIl-CMueGeNEX1 z5B48j)!e;#9pAbpg&&NMp1fG{LP_i!JVAH)D&Si$mHpLv>Hj5}Ek#A3lE7JGR+s{_o;1D=oVj?Iu=ClU(Mz$I)hQ zru3Zeoyu!8Rv&qOBv~zX!jFlsyN>72;nw4}F-}?*{P~Dr(#8wNyrefTS}Eu+=pO33 zyhicy##5Euo5R*^dS?5R+u5=?f6_K63SrYShKG--Kh9Cqson=8Vtd8@zt1OLvt z&KRzpUI|Yk99X4NuJiw6-@kk7r^&)`K^lA|`z_8L=McETDrI{7r(M~cW1J7_+17oT zwm*nH&CYZE)-5jtFYM=yiP>(F%D>|dBinqQn^R&>`UjtQ-uT`4*D@V}eXLE{artL! z=h!i1zS62>mXTpv%z7&B{fBq+zTNw0d-=_KcK-UJ`g;4gy$lQtY)RhkE(~Ds(|LD2 z0|NtRfk$L90|U1(2s1Lwnj^u$z`$PO>Fdh=gi};lQDT+rvJwUc2FViFh!W@g+}zZ> z5(ej@)Wnk16ovB4k_-iRPv3y>Mm}){2F5j>E{-7{$KOt~_h1T?Ile!=yktR#(hNl> z7n2U>78X|CJY6S22|d3@0x_<^9wu8@1ziQ!wOvqh^iXZt5~a(_7q%c^!uo?b8xHBj zIamC6{--?7>4NyZg8T~ky8GXM*WPFRKS}eM__-xP&sR@rd&0uFGeN*|`DUh7K^wSc z39}r?V?UJ0aA4ugZf-nVM)V&TURL;j!K@GVH3 z`M>AGZ>|?tUsSd;W&gf=*9%+evt_>yUE0{}(8VjMbH>;r?lar;uOc>afjefheR`!7 z|3Jgj$c=-Pq#!;g0 f_~w)SG5M?Jo5RlP-e};S1xgK`u6{1-oD!M