mirror of
https://github.com/aseprite/aseprite.git
synced 2024-10-02 21:12:10 +00:00
Add Brush::clone() functions to fix/simplify some Brush-related code
This refactor includes:
- In Lua now we can clone a custom brush with Brush(Image) and the new
brush doesn't share the image with the original one (added a new test
for this).
- Avoid creating extra images when it's not needed using
Brush::cloneWithExistingImages() (we can inject existing images in
the brush itself).
- Delete Brush-copy contructor & assign operator to use
Brush::clone() functions instead (which are more explicit).
- Some code from 12d8135264
(#4023)
reverted to avoid recreating brushes on left-click or in the brush
preview, i.e. moving the mouse (#4013 refers only to right-click, so
only on right-click we have to adjust the custom brush).
This commit is contained in:
parent
12d8135264
commit
92edd5f700
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (c) 2023 Igara Studio S.A.
|
||||
// Copyright (c) 2023-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -156,9 +156,7 @@ void ChangeBrushCommand::onExecute(Context* context)
|
||||
|
||||
// Create a copy of the brush (to avoid modifying the original
|
||||
// brush from the AppBrushes stock)
|
||||
BrushRef newBrush = std::make_shared<Brush>(*brush);
|
||||
newBrush->setImage(newImg.get(),
|
||||
newMsk.get());
|
||||
BrushRef newBrush = brush->cloneWithExistingImages(newImg, newMsk);
|
||||
contextBar->setActiveBrush(newBrush);
|
||||
}
|
||||
else {
|
||||
@ -210,9 +208,7 @@ void ChangeBrushCommand::onExecute(Context* context)
|
||||
break;
|
||||
}
|
||||
|
||||
BrushRef newBrush = std::make_shared<Brush>(*brush);
|
||||
newBrush->setImage(newImg.get(),
|
||||
newMsk.get());
|
||||
BrushRef newBrush = brush->cloneWithExistingImages(newImg, newMsk);
|
||||
contextBar->setActiveBrush(newBrush);
|
||||
}
|
||||
else {
|
||||
@ -297,10 +293,7 @@ void ChangeBrushCommand::onExecute(Context* context)
|
||||
|
||||
ImageRef newImg2(crop_image(newImg.get(), cropBounds, bg));
|
||||
ImageRef newMsk2(crop_image(newMsk.get(), cropBounds, bg));
|
||||
|
||||
BrushRef newBrush = std::make_shared<Brush>(*brush);
|
||||
newBrush->setImage(newImg.get(),
|
||||
newMsk.get());
|
||||
BrushRef newBrush = brush->cloneWithExistingImages(newImg2, newMsk2);
|
||||
contextBar->setActiveBrush(newBrush);
|
||||
}
|
||||
break;
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
@ -36,7 +36,7 @@ BrushRef Brush_new(lua_State* L, int index)
|
||||
if (auto brush2 = may_get_obj<BrushObj>(L, index)) {
|
||||
ASSERT(brush2->brush);
|
||||
if (brush2->brush)
|
||||
brush.reset(new Brush(*brush2->brush));
|
||||
brush = brush2->brush->cloneWithNewImages();
|
||||
}
|
||||
else if (auto image = may_get_image_from_arg(L, index)) {
|
||||
if (image) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -44,7 +44,8 @@ using namespace filters;
|
||||
ToolLoopManager::ToolLoopManager(ToolLoop* toolLoop)
|
||||
: m_toolLoop(toolLoop)
|
||||
, m_canceled(false)
|
||||
, m_brush0(*toolLoop->getBrush())
|
||||
, m_brushSize0(toolLoop->getBrush()->size())
|
||||
, m_brushAngle0(toolLoop->getBrush()->angle())
|
||||
, m_dynamics(toolLoop->getDynamics())
|
||||
{
|
||||
}
|
||||
@ -358,8 +359,8 @@ Stroke::Pt ToolLoopManager::getSpriteStrokePt(const Pointer& pointer)
|
||||
{
|
||||
// Convert the screen point to a sprite point
|
||||
Stroke::Pt spritePoint = pointer.point();
|
||||
spritePoint.size = m_brush0.size();
|
||||
spritePoint.angle = m_brush0.angle();
|
||||
spritePoint.size = m_brushSize0;
|
||||
spritePoint.angle = m_brushAngle0;
|
||||
|
||||
// Center the input to some grid point if needed
|
||||
snapToGrid(spritePoint);
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019-2021 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -95,7 +95,8 @@ private:
|
||||
Pointer m_lastPointer;
|
||||
gfx::Region m_dirtyArea;
|
||||
gfx::Region m_nextDirtyArea;
|
||||
doc::Brush m_brush0;
|
||||
const int m_brushSize0;
|
||||
const int m_brushAngle0;
|
||||
DynamicsOptions m_dynamics;
|
||||
gfx::PointF m_stabilizerCenter;
|
||||
};
|
||||
|
@ -467,7 +467,9 @@ os::SurfaceRef BrushPopup::createSurfaceForBrush(const BrushRef& origBrush,
|
||||
BrushRef brush = origBrush;
|
||||
if (brush) {
|
||||
if (brush->type() != kImageBrushType && brush->size() > kMaxSize) {
|
||||
brush.reset(new Brush(*brush));
|
||||
// Clone with shared images, as setSize() will re-create the
|
||||
// images and the brush is no kImageBrushType anyway.
|
||||
brush = brush->cloneWithSharedImages();
|
||||
brush->setSize(kMaxSize);
|
||||
}
|
||||
// Show the original image in the popup (without the image colors
|
||||
|
@ -190,16 +190,31 @@ public:
|
||||
ASSERT(m_ink);
|
||||
ASSERT(m_controller);
|
||||
|
||||
if (m_brush->type() == kImageBrushType &&
|
||||
(m_button == Right || (m_button == Left && m_brush->isMonochromeImage()))) {
|
||||
m_brush->setImageColor(
|
||||
Brush::ImageColor::MainColor,
|
||||
// If the user right-clicks with a custom/image brush we change
|
||||
// the image's colors of the brush to the background color.
|
||||
//
|
||||
// This is different from SwitchColors that makes a new brush
|
||||
// switching fg <-> bg colors, so here we have some extra
|
||||
// functionality with custom brushes (quickly convert the custom
|
||||
// brush with a plain color, or in other words, replace the custom
|
||||
// brush area with the background color).
|
||||
if (m_brush->type() == kImageBrushType && m_button == Right) {
|
||||
// We've to recalculate the background color to use for the
|
||||
// brush using the specific brush image pixel format/color mode,
|
||||
// as we cannot use m_primaryColor or m_bgColor here because
|
||||
// those are in the sprite pixel format/color mode.
|
||||
const color_t brushColor =
|
||||
color_utils::color_for_target_mask(
|
||||
(m_button == Left ? Preferences::instance().colorBar.fgColor() :
|
||||
Preferences::instance().colorBar.bgColor()),
|
||||
Preferences::instance().colorBar.bgColor(),
|
||||
ColorTarget(ColorTarget::TransparentLayer,
|
||||
m_brush->image()->pixelFormat(),
|
||||
-1)));
|
||||
-1));
|
||||
|
||||
// Clone the brush with new images to avoid modifying the
|
||||
// current brush used in left-click / brush preview.
|
||||
BrushRef newBrush = m_brush->cloneWithNewImages();
|
||||
newBrush->setImageColor(Brush::ImageColor::BothColors, brushColor);
|
||||
m_brush = newBrush;
|
||||
}
|
||||
|
||||
if (m_tilesMode) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite Document Library
|
||||
// Copyright (C) 2019-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2016 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -48,32 +48,43 @@ Brush::Brush(BrushType type, int size, int angle)
|
||||
regenerate();
|
||||
}
|
||||
|
||||
Brush::Brush(const Brush& brush)
|
||||
{
|
||||
m_type = brush.m_type;
|
||||
m_size = brush.m_size;
|
||||
m_angle = brush.m_angle;
|
||||
m_image = brush.m_image;
|
||||
m_maskBitmap = brush.m_maskBitmap;
|
||||
m_pattern = brush.m_pattern;
|
||||
m_patternOrigin = brush.m_patternOrigin;
|
||||
m_gen = 0;
|
||||
|
||||
regenerate();
|
||||
}
|
||||
|
||||
Brush::~Brush()
|
||||
{
|
||||
clean();
|
||||
}
|
||||
|
||||
void Brush::setType(BrushType type)
|
||||
BrushRef Brush::cloneWithSharedImages() const
|
||||
{
|
||||
m_type = type;
|
||||
if (m_type != kImageBrushType)
|
||||
regenerate();
|
||||
BrushRef newBrush = std::make_shared<Brush>();
|
||||
newBrush->copyFieldsFromBrush(*this);
|
||||
return newBrush;
|
||||
}
|
||||
|
||||
BrushRef Brush::cloneWithNewImages() const
|
||||
{
|
||||
BrushRef newBrush = std::make_shared<Brush>();
|
||||
newBrush->copyFieldsFromBrush(*this);
|
||||
if (newBrush->m_image)
|
||||
newBrush->m_image.reset(Image::createCopy(newBrush->m_image.get()));
|
||||
if (newBrush->m_maskBitmap)
|
||||
newBrush->m_maskBitmap.reset(Image::createCopy(newBrush->m_maskBitmap.get()));
|
||||
return newBrush;
|
||||
}
|
||||
|
||||
BrushRef Brush::cloneWithExistingImages(const ImageRef& image,
|
||||
const ImageRef& maskBitmap) const
|
||||
{
|
||||
BrushRef newBrush = std::make_shared<Brush>();
|
||||
newBrush->copyFieldsFromBrush(*this);
|
||||
|
||||
newBrush->m_image = image;
|
||||
if (maskBitmap)
|
||||
newBrush->m_maskBitmap = maskBitmap;
|
||||
else
|
||||
clean();
|
||||
newBrush->regenerateMaskBitmap();
|
||||
|
||||
newBrush->resetBounds();
|
||||
return newBrush;
|
||||
}
|
||||
|
||||
void Brush::setSize(int size)
|
||||
@ -95,16 +106,8 @@ void Brush::setImage(const Image* image,
|
||||
m_image.reset(Image::createCopy(image));
|
||||
if (maskBitmap)
|
||||
m_maskBitmap.reset(Image::createCopy(maskBitmap));
|
||||
else {
|
||||
int w = image->width();
|
||||
int h = image->height();
|
||||
m_maskBitmap.reset(Image::create(IMAGE_BITMAP, w, h));
|
||||
LockImageBits<BitmapTraits> bits(m_maskBitmap.get());
|
||||
auto pos = bits.begin();
|
||||
for (int v=0; v<h; ++v)
|
||||
for (int u=0; u<w; ++u, ++pos)
|
||||
*pos = (get_pixel(image, u, v) != image->maskColor());
|
||||
}
|
||||
else
|
||||
regenerateMaskBitmap();
|
||||
|
||||
m_backupImage.reset();
|
||||
m_mainColor.reset();
|
||||
@ -234,7 +237,8 @@ static void replace_image_colors_indexed(
|
||||
}
|
||||
}
|
||||
|
||||
void Brush::setImageColor(ImageColor imageColor, color_t color)
|
||||
void Brush::setImageColor(const ImageColor imageColor,
|
||||
const color_t color)
|
||||
{
|
||||
ASSERT(m_image);
|
||||
if (!m_image)
|
||||
@ -249,10 +253,13 @@ void Brush::setImageColor(ImageColor imageColor, color_t color)
|
||||
|
||||
switch (imageColor) {
|
||||
case ImageColor::MainColor:
|
||||
m_mainColor = color_t(color);
|
||||
m_mainColor = color;
|
||||
break;
|
||||
case ImageColor::BackgroundColor:
|
||||
m_bgColor = color_t(color);
|
||||
m_bgColor = color;
|
||||
break;
|
||||
case ImageColor::BothColors:
|
||||
m_mainColor = m_bgColor = color;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -379,6 +386,22 @@ void Brush::regenerate()
|
||||
}
|
||||
}
|
||||
|
||||
void Brush::regenerateMaskBitmap()
|
||||
{
|
||||
ASSERT(m_image);
|
||||
if (!m_image)
|
||||
return;
|
||||
|
||||
int w = m_image->width();
|
||||
int h = m_image->height();
|
||||
m_maskBitmap.reset(Image::create(IMAGE_BITMAP, w, h));
|
||||
LockImageBits<BitmapTraits> bits(m_maskBitmap.get());
|
||||
auto pos = bits.begin();
|
||||
for (int v=0; v<h; ++v)
|
||||
for (int u=0; u<w; ++u, ++pos)
|
||||
*pos = (get_pixel(m_image.get(), u, v) != m_image->maskColor());
|
||||
}
|
||||
|
||||
void Brush::resetBounds()
|
||||
{
|
||||
m_center = gfx::Point(std::max(0, m_image->width()/2),
|
||||
@ -388,4 +411,19 @@ void Brush::resetBounds()
|
||||
m_image->height()));
|
||||
}
|
||||
|
||||
void Brush::copyFieldsFromBrush(const Brush& brush)
|
||||
{
|
||||
m_type = brush.m_type;
|
||||
m_size = brush.m_size;
|
||||
m_angle = brush.m_angle;
|
||||
m_image = brush.m_image;
|
||||
m_maskBitmap = brush.m_maskBitmap;
|
||||
m_bounds = brush.m_bounds;
|
||||
m_center = brush.m_center;
|
||||
m_pattern = brush.m_pattern;
|
||||
m_patternOrigin = brush.m_patternOrigin;
|
||||
m_patternImage = brush.m_patternImage;
|
||||
m_gen = 0;
|
||||
}
|
||||
|
||||
} // namespace doc
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite Document Library
|
||||
// Copyright (C) 2019-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -22,18 +22,34 @@
|
||||
|
||||
namespace doc {
|
||||
|
||||
class Brush;
|
||||
using BrushRef = std::shared_ptr<Brush>;
|
||||
|
||||
class Brush {
|
||||
public:
|
||||
static const int kMinBrushSize = 1;
|
||||
static const int kMaxBrushSize = 64;
|
||||
|
||||
enum class ImageColor { MainColor, BackgroundColor };
|
||||
enum class ImageColor { MainColor, BackgroundColor, BothColors };
|
||||
|
||||
Brush();
|
||||
Brush(BrushType type, int size, int angle);
|
||||
Brush(const Brush& brush);
|
||||
~Brush();
|
||||
|
||||
// Don't offer copy constructor/operator, use clone*() functions
|
||||
// instead.
|
||||
Brush(const Brush&) = delete;
|
||||
Brush& operator=(const Brush&) = delete;
|
||||
|
||||
// Cloned brushes can share the same image until
|
||||
// setSize()/Angle()/etc. (regenerate()) is called for the new
|
||||
// brush. In that case the original brush and the cloned one will
|
||||
// have a different image after all.
|
||||
BrushRef cloneWithSharedImages() const;
|
||||
BrushRef cloneWithNewImages() const;
|
||||
BrushRef cloneWithExistingImages(const ImageRef& image,
|
||||
const ImageRef& maskBitmap) const;
|
||||
|
||||
BrushType type() const { return m_type; }
|
||||
int size() const { return m_size; }
|
||||
int angle() const { return m_angle; }
|
||||
@ -48,7 +64,6 @@ namespace doc {
|
||||
const gfx::Rect& bounds() const { return m_bounds; }
|
||||
const gfx::Point& center() const { return m_center; }
|
||||
|
||||
void setType(BrushType type);
|
||||
void setSize(int size);
|
||||
void setAngle(int angle);
|
||||
void setImage(const Image* image,
|
||||
@ -78,14 +93,12 @@ namespace doc {
|
||||
return m_image.get();
|
||||
}
|
||||
|
||||
const bool isMonochromeImage() const {
|
||||
return m_mainColor.has_value();
|
||||
}
|
||||
|
||||
private:
|
||||
void clean();
|
||||
void regenerate();
|
||||
void regenerateMaskBitmap();
|
||||
void resetBounds();
|
||||
void copyFieldsFromBrush(const Brush& brush);
|
||||
|
||||
BrushType m_type; // Type of brush
|
||||
int m_size; // Size (diameter)
|
||||
@ -105,8 +118,6 @@ namespace doc {
|
||||
std::optional<color_t> m_bgColor; // Background color
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<Brush> BrushRef;
|
||||
|
||||
} // namespace doc
|
||||
|
||||
#endif
|
||||
|
@ -1,4 +1,4 @@
|
||||
-- Copyright (C) 2019-2023 Igara Studio S.A.
|
||||
-- Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
--
|
||||
-- This file is released under the terms of the MIT license.
|
||||
-- Read LICENSE.txt for more information.
|
||||
@ -51,6 +51,7 @@ do
|
||||
assert(b.patternOrigin.y == 0)
|
||||
end
|
||||
|
||||
-- Image brush
|
||||
do
|
||||
local rgba = app.pixelColor.rgba
|
||||
local r = rgba(255, 0, 0)
|
||||
@ -71,6 +72,15 @@ do
|
||||
|
||||
brush:setBgColor(b)
|
||||
expect_img(brush.image, { b, g, g, b })
|
||||
|
||||
-- Test copy image brushes
|
||||
local brush2 = Brush(brush)
|
||||
expect_img(brush2.image, { b, g, g, b })
|
||||
brush2:setFgColor(r)
|
||||
expect_img(brush2.image, { b, r, r, b })
|
||||
brush2:setBgColor(r)
|
||||
expect_img(brush2.image, { r, r, r, r })
|
||||
expect_img(brush.image, { b, g, g, b }) -- First brush wasn't modified
|
||||
end
|
||||
|
||||
-- Tests with Image Brushes
|
||||
|
Loading…
Reference in New Issue
Block a user