mirror of
https://github.com/aseprite/aseprite.git
synced 2025-04-16 05:42:32 +00:00
Add command to Stroke the selection bounds w/the foreground color
This commit is contained in:
parent
291e9c7dde
commit
96ca735ed5
@ -40,6 +40,7 @@
|
|||||||
<key command="Clear" shortcut="Del" />
|
<key command="Clear" shortcut="Del" />
|
||||||
<key command="Clear" shortcut="Backspace" />
|
<key command="Clear" shortcut="Backspace" />
|
||||||
<key command="Fill" shortcut="F" />
|
<key command="Fill" shortcut="F" />
|
||||||
|
<key command="Stroke" shortcut="S" />
|
||||||
<key command="Flip" shortcut="Shift+H">
|
<key command="Flip" shortcut="Shift+H">
|
||||||
<param name="target" value="mask" />
|
<param name="target" value="mask" />
|
||||||
<param name="orientation" value="horizontal" />
|
<param name="orientation" value="horizontal" />
|
||||||
@ -593,6 +594,7 @@
|
|||||||
<item command="Clear" text="@.edit_clear" />
|
<item command="Clear" text="@.edit_clear" />
|
||||||
<separator />
|
<separator />
|
||||||
<item command="Fill" text="@.edit_fill" />
|
<item command="Fill" text="@.edit_fill" />
|
||||||
|
<item command="Stroke" text="@.edit_stroke" />
|
||||||
<separator />
|
<separator />
|
||||||
<menu text="@.edit_rotate">
|
<menu text="@.edit_rotate">
|
||||||
<item command="Rotate" text="@.edit_rotate_180">
|
<item command="Rotate" text="@.edit_rotate_180">
|
||||||
|
@ -379,6 +379,7 @@ SliceProperties = Slice Properties
|
|||||||
SnapToGrid = Snap to Grid
|
SnapToGrid = Snap to Grid
|
||||||
SpriteProperties = Sprite Properties
|
SpriteProperties = Sprite Properties
|
||||||
SpriteSize = Sprite Size
|
SpriteSize = Sprite Size
|
||||||
|
Stroke = Stroke Selection Borders with Foreground Color
|
||||||
SwitchColors = Switch Colors
|
SwitchColors = Switch Colors
|
||||||
SwitchNonactiveLayersOpacity = Switch Nonactive Layers Opacity
|
SwitchNonactiveLayersOpacity = Switch Nonactive Layers Opacity
|
||||||
SymmetryMode = Symmetry Mode
|
SymmetryMode = Symmetry Mode
|
||||||
@ -638,6 +639,7 @@ edit_copy_merged = Copy Mer&ged
|
|||||||
edit_paste = &Paste
|
edit_paste = &Paste
|
||||||
edit_clear = &Delete
|
edit_clear = &Delete
|
||||||
edit_fill = &Fill
|
edit_fill = &Fill
|
||||||
|
edit_stroke = Stroke
|
||||||
edit_rotate = R&otate
|
edit_rotate = R&otate
|
||||||
edit_rotate_180 = &180
|
edit_rotate_180 = &180
|
||||||
edit_rotate_90cw = &90 CW
|
edit_rotate_90cw = &90 CW
|
||||||
|
@ -196,7 +196,7 @@ if(ENABLE_UI)
|
|||||||
commands/cmd_exit.cpp
|
commands/cmd_exit.cpp
|
||||||
commands/cmd_export_sprite_sheet.cpp
|
commands/cmd_export_sprite_sheet.cpp
|
||||||
commands/cmd_eyedropper.cpp
|
commands/cmd_eyedropper.cpp
|
||||||
commands/cmd_fill.cpp
|
commands/cmd_fill_and_stroke.cpp
|
||||||
commands/cmd_fit_screen.cpp
|
commands/cmd_fit_screen.cpp
|
||||||
commands/cmd_flatten_layers.cpp
|
commands/cmd_flatten_layers.cpp
|
||||||
commands/cmd_flip.cpp
|
commands/cmd_flip.cpp
|
||||||
@ -391,7 +391,6 @@ if(ENABLE_UI)
|
|||||||
ui_context.cpp
|
ui_context.cpp
|
||||||
util/clipboard.cpp
|
util/clipboard.cpp
|
||||||
util/clipboard_native.cpp
|
util/clipboard_native.cpp
|
||||||
util/fill_selection.cpp
|
|
||||||
widget_loader.cpp)
|
widget_loader.cpp)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
#include "app/cmd/clear_cel.h"
|
#include "app/cmd/clear_cel.h"
|
||||||
#include "app/document.h"
|
#include "app/document.h"
|
||||||
#include "app/util/fill_selection.h"
|
#include "doc/algorithm/fill_selection.h"
|
||||||
#include "doc/cel.h"
|
#include "doc/cel.h"
|
||||||
#include "doc/image_impl.h"
|
#include "doc/image_impl.h"
|
||||||
#include "doc/layer.h"
|
#include "doc/layer.h"
|
||||||
@ -89,7 +89,7 @@ void ClearMask::clear()
|
|||||||
app::Document* doc = static_cast<app::Document*>(cel->document());
|
app::Document* doc = static_cast<app::Document*>(cel->document());
|
||||||
Mask* mask = doc->mask();
|
Mask* mask = doc->mask();
|
||||||
|
|
||||||
fill_selection(image, m_offset, mask, m_bgcolor);
|
doc::algorithm::fill_selection(image, m_offset, mask, m_bgcolor);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClearMask::restore()
|
void ClearMask::restore()
|
||||||
|
@ -19,23 +19,28 @@
|
|||||||
#include "app/transaction.h"
|
#include "app/transaction.h"
|
||||||
#include "app/ui/editor/editor.h"
|
#include "app/ui/editor/editor.h"
|
||||||
#include "app/util/expand_cel_canvas.h"
|
#include "app/util/expand_cel_canvas.h"
|
||||||
#include "app/util/fill_selection.h"
|
#include "doc/algorithm/fill_selection.h"
|
||||||
|
#include "doc/algorithm/stroke_selection.h"
|
||||||
#include "doc/mask.h"
|
#include "doc/mask.h"
|
||||||
|
|
||||||
namespace app {
|
namespace app {
|
||||||
|
|
||||||
class FillCommand : public Command {
|
class FillCommand : public Command {
|
||||||
public:
|
public:
|
||||||
FillCommand();
|
enum Type { Fill, Stroke };
|
||||||
|
FillCommand(Type type);
|
||||||
Command* clone() const override { return new FillCommand(*this); }
|
Command* clone() const override { return new FillCommand(*this); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool onEnabled(Context* ctx) override;
|
bool onEnabled(Context* ctx) override;
|
||||||
void onExecute(Context* ctx) override;
|
void onExecute(Context* ctx) override;
|
||||||
|
private:
|
||||||
|
Type m_type;
|
||||||
};
|
};
|
||||||
|
|
||||||
FillCommand::FillCommand()
|
FillCommand::FillCommand(Type type)
|
||||||
: Command(CommandId::Fill(), CmdUIOnlyFlag)
|
: Command(type == Stroke ? CommandId::Stroke():
|
||||||
|
CommandId::Fill(), CmdUIOnlyFlag)
|
||||||
|
, m_type(type)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,10 +66,11 @@ void FillCommand::onExecute(Context* ctx)
|
|||||||
Site site = *writer.site();
|
Site site = *writer.site();
|
||||||
Document* doc = (app::Document*)site.document();
|
Document* doc = (app::Document*)site.document();
|
||||||
Sprite* sprite = site.sprite();
|
Sprite* sprite = site.sprite();
|
||||||
Cel* cel = site.cel();
|
Layer* layer = site.layer();
|
||||||
Mask* mask = doc->mask();
|
Mask* mask = doc->mask();
|
||||||
if (!doc || !sprite || !cel || !mask ||
|
if (!doc || !sprite ||
|
||||||
!doc->isMaskVisible())
|
!layer || !layer->isImage() ||
|
||||||
|
!mask || !doc->isMaskVisible())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Preferences& pref = Preferences::instance();
|
Preferences& pref = Preferences::instance();
|
||||||
@ -74,7 +80,7 @@ void FillCommand::onExecute(Context* ctx)
|
|||||||
Transaction transaction(writer.context(), "Fill Selection with Foreground Color");
|
Transaction transaction(writer.context(), "Fill Selection with Foreground Color");
|
||||||
{
|
{
|
||||||
ExpandCelCanvas expand(
|
ExpandCelCanvas expand(
|
||||||
site, cel->layer(),
|
site, layer,
|
||||||
TiledMode::NONE, transaction,
|
TiledMode::NONE, transaction,
|
||||||
ExpandCelCanvas::None);
|
ExpandCelCanvas::None);
|
||||||
|
|
||||||
@ -84,19 +90,31 @@ void FillCommand::onExecute(Context* ctx)
|
|||||||
|
|
||||||
const gfx::Point offset = (mask->bounds().origin()
|
const gfx::Point offset = (mask->bounds().origin()
|
||||||
- expand.getCel()->position());
|
- expand.getCel()->position());
|
||||||
|
const doc::color_t docColor =
|
||||||
|
color_utils::color_for_layer(
|
||||||
|
color, layer);
|
||||||
|
|
||||||
fill_selection(expand.getDestCanvas(),
|
if (m_type == Stroke) {
|
||||||
offset,
|
doc::algorithm::stroke_selection(
|
||||||
mask,
|
expand.getDestCanvas(),
|
||||||
color_utils::color_for_layer(color,
|
offset,
|
||||||
cel->layer()));
|
mask,
|
||||||
|
docColor);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
doc::algorithm::fill_selection(
|
||||||
|
expand.getDestCanvas(),
|
||||||
|
offset,
|
||||||
|
mask,
|
||||||
|
docColor);
|
||||||
|
}
|
||||||
|
|
||||||
expand.commit();
|
expand.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the cel wasn't deleted by cmd::ClearMask, we trim it.
|
// If the cel wasn't deleted by cmd::ClearMask, we trim it.
|
||||||
cel = ctx->activeSite().cel();
|
Cel* cel = ctx->activeSite().cel();
|
||||||
if (cel && cel->layer()->isTransparent())
|
if (cel && layer->isTransparent())
|
||||||
transaction.execute(new cmd::TrimCel(cel));
|
transaction.execute(new cmd::TrimCel(cel));
|
||||||
|
|
||||||
transaction.commit();
|
transaction.commit();
|
||||||
@ -107,7 +125,12 @@ void FillCommand::onExecute(Context* ctx)
|
|||||||
|
|
||||||
Command* CommandFactory::createFillCommand()
|
Command* CommandFactory::createFillCommand()
|
||||||
{
|
{
|
||||||
return new FillCommand;
|
return new FillCommand(FillCommand::Fill);
|
||||||
|
}
|
||||||
|
|
||||||
|
Command* CommandFactory::createStrokeCommand()
|
||||||
|
{
|
||||||
|
return new FillCommand(FillCommand::Stroke);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace app
|
} // namespace app
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2015-2017 David Capello
|
// Copyright (C) 2015-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
// the End-User License Agreement for Aseprite.
|
// the End-User License Agreement for Aseprite.
|
||||||
@ -18,6 +18,7 @@
|
|||||||
#include "app/pref/preferences.h"
|
#include "app/pref/preferences.h"
|
||||||
#include "app/transaction.h"
|
#include "app/transaction.h"
|
||||||
#include "base/convert_to.h"
|
#include "base/convert_to.h"
|
||||||
|
#include "doc/algorithm/modify_selection.h"
|
||||||
#include "doc/brush_type.h"
|
#include "doc/brush_type.h"
|
||||||
#include "doc/mask.h"
|
#include "doc/mask.h"
|
||||||
#include "filters/neighboring_pixels.h"
|
#include "filters/neighboring_pixels.h"
|
||||||
@ -30,14 +31,13 @@
|
|||||||
namespace app {
|
namespace app {
|
||||||
|
|
||||||
using namespace doc;
|
using namespace doc;
|
||||||
|
typedef doc::algorithm::SelectionModifier Modifier;
|
||||||
|
|
||||||
class ModifySelectionWindow : public app::gen::ModifySelection {
|
class ModifySelectionWindow : public app::gen::ModifySelection {
|
||||||
};
|
};
|
||||||
|
|
||||||
class ModifySelectionCommand : public Command {
|
class ModifySelectionCommand : public Command {
|
||||||
public:
|
public:
|
||||||
enum Modifier { Border, Expand, Contract };
|
|
||||||
|
|
||||||
ModifySelectionCommand();
|
ModifySelectionCommand();
|
||||||
Command* clone() const override { return new ModifySelectionCommand(*this); }
|
Command* clone() const override { return new ModifySelectionCommand(*this); }
|
||||||
|
|
||||||
@ -49,9 +49,6 @@ protected:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
std::string getActionName() const;
|
std::string getActionName() const;
|
||||||
void applyModifier(const Mask* srcMask, Mask* dstMask,
|
|
||||||
const int brushRadius,
|
|
||||||
const doc::BrushType brushType) const;
|
|
||||||
|
|
||||||
Modifier m_modifier;
|
Modifier m_modifier;
|
||||||
int m_quantity;
|
int m_quantity;
|
||||||
@ -60,7 +57,7 @@ private:
|
|||||||
|
|
||||||
ModifySelectionCommand::ModifySelectionCommand()
|
ModifySelectionCommand::ModifySelectionCommand()
|
||||||
: Command(CommandId::ModifySelection(), CmdRecordableFlag)
|
: Command(CommandId::ModifySelection(), CmdRecordableFlag)
|
||||||
, m_modifier(Expand)
|
, m_modifier(Modifier::Expand)
|
||||||
, m_quantity(0)
|
, m_quantity(0)
|
||||||
, m_brushType(doc::kCircleBrushType)
|
, m_brushType(doc::kCircleBrushType)
|
||||||
{
|
{
|
||||||
@ -69,9 +66,9 @@ ModifySelectionCommand::ModifySelectionCommand()
|
|||||||
void ModifySelectionCommand::onLoadParams(const Params& params)
|
void ModifySelectionCommand::onLoadParams(const Params& params)
|
||||||
{
|
{
|
||||||
const std::string modifier = params.get("modifier");
|
const std::string modifier = params.get("modifier");
|
||||||
if (modifier == "border") m_modifier = Border;
|
if (modifier == "border") m_modifier = Modifier::Border;
|
||||||
else if (modifier == "expand") m_modifier = Expand;
|
else if (modifier == "expand") m_modifier = Modifier::Expand;
|
||||||
else if (modifier == "contract") m_modifier = Contract;
|
else if (modifier == "contract") m_modifier = Modifier::Contract;
|
||||||
|
|
||||||
const int quantity = params.get_as<int>("quantity");
|
const int quantity = params.get_as<int>("quantity");
|
||||||
m_quantity = std::max<int>(0, quantity);
|
m_quantity = std::max<int>(0, quantity);
|
||||||
@ -97,7 +94,7 @@ void ModifySelectionCommand::onExecute(Context* context)
|
|||||||
ModifySelectionWindow window;
|
ModifySelectionWindow window;
|
||||||
|
|
||||||
window.setText(getActionName() + " Selection");
|
window.setText(getActionName() + " Selection");
|
||||||
if (m_modifier == Border)
|
if (m_modifier == Modifier::Border)
|
||||||
window.byLabel()->setText("Width:");
|
window.byLabel()->setText("Width:");
|
||||||
else
|
else
|
||||||
window.byLabel()->setText(getActionName() + " By:");
|
window.byLabel()->setText(getActionName() + " By:");
|
||||||
@ -131,11 +128,12 @@ void ModifySelectionCommand::onExecute(Context* context)
|
|||||||
Document* document(writer.document());
|
Document* document(writer.document());
|
||||||
Sprite* sprite(writer.sprite());
|
Sprite* sprite(writer.sprite());
|
||||||
|
|
||||||
base::UniquePtr<Mask> mask(new Mask());
|
base::UniquePtr<Mask> mask(new Mask);
|
||||||
{
|
{
|
||||||
mask->reserve(sprite->bounds());
|
mask->reserve(sprite->bounds());
|
||||||
mask->freeze();
|
mask->freeze();
|
||||||
applyModifier(document->mask(), mask, quantity, brush);
|
doc::algorithm::modify_selection(
|
||||||
|
m_modifier, document->mask(), mask, quantity, brush);
|
||||||
mask->unfreeze();
|
mask->unfreeze();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,81 +162,13 @@ std::string ModifySelectionCommand::onGetFriendlyName() const
|
|||||||
std::string ModifySelectionCommand::getActionName() const
|
std::string ModifySelectionCommand::getActionName() const
|
||||||
{
|
{
|
||||||
switch (m_modifier) {
|
switch (m_modifier) {
|
||||||
case Border: return Strings::commands_ModifySelection_Border();
|
case Modifier::Border: return Strings::commands_ModifySelection_Border();
|
||||||
case Expand: return Strings::commands_ModifySelection_Expand();
|
case Modifier::Expand: return Strings::commands_ModifySelection_Expand();
|
||||||
case Contract: return Strings::commands_ModifySelection_Contract();
|
case Modifier::Contract: return Strings::commands_ModifySelection_Contract();
|
||||||
default: return Strings::commands_ModifySelection_Modify();
|
default: return Strings::commands_ModifySelection_Modify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO create morphological operators/functions in "doc" namespace
|
|
||||||
// TODO the impl is not optimal, but is good enough as a first version
|
|
||||||
void ModifySelectionCommand::applyModifier(const Mask* srcMask, Mask* dstMask,
|
|
||||||
const int radius,
|
|
||||||
const doc::BrushType brush) const
|
|
||||||
{
|
|
||||||
const doc::Image* srcImage = srcMask->bitmap();
|
|
||||||
doc::Image* dstImage = dstMask->bitmap();
|
|
||||||
|
|
||||||
// Image bounds to clip get/put pixels
|
|
||||||
const gfx::Rect srcBounds = srcImage->bounds();
|
|
||||||
|
|
||||||
// Create a kernel
|
|
||||||
const int size = 2*radius+1;
|
|
||||||
base::UniquePtr<doc::Image> kernel(doc::Image::create(IMAGE_BITMAP, size, size));
|
|
||||||
doc::clear_image(kernel, 0);
|
|
||||||
if (brush == doc::kCircleBrushType)
|
|
||||||
doc::fill_ellipse(kernel, 0, 0, size-1, size-1, 1);
|
|
||||||
else
|
|
||||||
doc::fill_rect(kernel, 0, 0, size-1, size-1, 1);
|
|
||||||
doc::put_pixel(kernel, radius, radius, 0);
|
|
||||||
|
|
||||||
int total = 0; // Number of 1s in the kernel image
|
|
||||||
for (int v=0; v<size; ++v)
|
|
||||||
for (int u=0; u<size; ++u)
|
|
||||||
total += kernel->getPixel(u, v);
|
|
||||||
|
|
||||||
for (int y=-radius; y<srcBounds.h+radius; ++y) {
|
|
||||||
for (int x=-radius; x<srcBounds.w+radius; ++x) {
|
|
||||||
doc::color_t c;
|
|
||||||
if (srcBounds.contains(x, y))
|
|
||||||
c = srcImage->getPixel(x, y);
|
|
||||||
else
|
|
||||||
c = 0;
|
|
||||||
|
|
||||||
int accum = 0;
|
|
||||||
for (int v=0; v<size; ++v) {
|
|
||||||
for (int u=0; u<size; ++u) {
|
|
||||||
if (kernel->getPixel(u, v)) {
|
|
||||||
if (srcBounds.contains(x+u-radius, y+v-radius))
|
|
||||||
accum += srcImage->getPixel(x-radius+u, y-radius+v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (m_modifier) {
|
|
||||||
case Border: {
|
|
||||||
c = (c && accum < total) ? 1: 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Expand: {
|
|
||||||
c = (c || accum > 0) ? 1: 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Contract: {
|
|
||||||
c = (c && accum == total) ? 1: 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c)
|
|
||||||
doc::put_pixel(dstImage,
|
|
||||||
srcMask->bounds().x+x,
|
|
||||||
srcMask->bounds().y+y, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Command* CommandFactory::createModifySelectionCommand()
|
Command* CommandFactory::createModifySelectionCommand()
|
||||||
{
|
{
|
||||||
return new ModifySelectionCommand;
|
return new ModifySelectionCommand;
|
||||||
|
@ -139,6 +139,7 @@ FOR_EACH_COMMAND(ShowSlices)
|
|||||||
FOR_EACH_COMMAND(SliceProperties)
|
FOR_EACH_COMMAND(SliceProperties)
|
||||||
FOR_EACH_COMMAND(SnapToGrid)
|
FOR_EACH_COMMAND(SnapToGrid)
|
||||||
FOR_EACH_COMMAND(SpriteProperties)
|
FOR_EACH_COMMAND(SpriteProperties)
|
||||||
|
FOR_EACH_COMMAND(Stroke)
|
||||||
FOR_EACH_COMMAND(SwitchColors)
|
FOR_EACH_COMMAND(SwitchColors)
|
||||||
FOR_EACH_COMMAND(SymmetryMode)
|
FOR_EACH_COMMAND(SymmetryMode)
|
||||||
FOR_EACH_COMMAND(TiledMode)
|
FOR_EACH_COMMAND(TiledMode)
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
// Aseprite
|
|
||||||
// Copyright (C) 2018 David Capello
|
|
||||||
//
|
|
||||||
// This program is distributed under the terms of
|
|
||||||
// the End-User License Agreement for Aseprite.
|
|
||||||
|
|
||||||
#ifndef APP_UTIL_FILL_SELECTION_H_INCLUDED
|
|
||||||
#define APP_UTIL_FILL_SELECTION_H_INCLUDED
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "doc/color.h"
|
|
||||||
#include "gfx/point.h"
|
|
||||||
|
|
||||||
namespace doc {
|
|
||||||
class Image;
|
|
||||||
class Mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace app {
|
|
||||||
|
|
||||||
void fill_selection(doc::Image* image,
|
|
||||||
const gfx::Point& offset,
|
|
||||||
const doc::Mask* mask,
|
|
||||||
const doc::color_t color);
|
|
||||||
|
|
||||||
} // namespace app
|
|
||||||
|
|
||||||
#endif
|
|
@ -7,14 +7,17 @@ endif()
|
|||||||
|
|
||||||
add_library(doc-lib
|
add_library(doc-lib
|
||||||
algo.cpp
|
algo.cpp
|
||||||
|
algorithm/fill_selection.cpp
|
||||||
algorithm/flip_image.cpp
|
algorithm/flip_image.cpp
|
||||||
algorithm/floodfill.cpp
|
algorithm/floodfill.cpp
|
||||||
|
algorithm/modify_selection.cpp
|
||||||
algorithm/polygon.cpp
|
algorithm/polygon.cpp
|
||||||
algorithm/resize_image.cpp
|
algorithm/resize_image.cpp
|
||||||
algorithm/rotate.cpp
|
algorithm/rotate.cpp
|
||||||
algorithm/rotsprite.cpp
|
algorithm/rotsprite.cpp
|
||||||
algorithm/shift_image.cpp
|
algorithm/shift_image.cpp
|
||||||
algorithm/shrink_bounds.cpp
|
algorithm/shrink_bounds.cpp
|
||||||
|
algorithm/stroke_selection.cpp
|
||||||
anidir.cpp
|
anidir.cpp
|
||||||
blend_funcs.cpp
|
blend_funcs.cpp
|
||||||
blend_mode.cpp
|
blend_mode.cpp
|
||||||
|
@ -1,30 +1,30 @@
|
|||||||
// Aseprite
|
// Aseprite Document Library
|
||||||
// Copyright (C) 2018 David Capello
|
// Copyright (c) 2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This file is released under the terms of the MIT license.
|
||||||
// the End-User License Agreement for Aseprite.
|
// Read LICENSE.txt for more information.
|
||||||
|
|
||||||
#ifdef HAVE_CONFIG_H
|
#ifdef HAVE_CONFIG_H
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "app/util/fill_selection.h"
|
#include "doc/algorithm/fill_selection.h"
|
||||||
|
|
||||||
#include "doc/image_impl.h"
|
#include "doc/image_impl.h"
|
||||||
#include "doc/mask.h"
|
#include "doc/mask.h"
|
||||||
#include "doc/primitives.h"
|
#include "doc/primitives.h"
|
||||||
|
|
||||||
namespace app {
|
namespace doc {
|
||||||
|
namespace algorithm {
|
||||||
using namespace doc;
|
|
||||||
|
|
||||||
void fill_selection(Image* image,
|
void fill_selection(Image* image,
|
||||||
const gfx::Point& offset,
|
const gfx::Point& offset,
|
||||||
const Mask* mask,
|
const Mask* mask,
|
||||||
const color_t color)
|
const color_t color)
|
||||||
{
|
{
|
||||||
|
ASSERT(mask);
|
||||||
ASSERT(mask->bitmap());
|
ASSERT(mask->bitmap());
|
||||||
if (!mask->bitmap())
|
if (!mask || !mask->bitmap())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const LockImageBits<BitmapTraits> maskBits(mask->bitmap());
|
const LockImageBits<BitmapTraits> maskBits(mask->bitmap());
|
||||||
@ -45,4 +45,5 @@ void fill_selection(Image* image,
|
|||||||
ASSERT(it == maskBits.end());
|
ASSERT(it == maskBits.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace app
|
} // namespace algorithm
|
||||||
|
} // namespace doc
|
27
src/doc/algorithm/fill_selection.h
Normal file
27
src/doc/algorithm/fill_selection.h
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// Aseprite Document Library
|
||||||
|
// Copyright (c) 2018 David Capello
|
||||||
|
//
|
||||||
|
// This file is released under the terms of the MIT license.
|
||||||
|
// Read LICENSE.txt for more information.
|
||||||
|
|
||||||
|
#ifndef DOC_ALGORITHM_FILL_SELECTION_H_INCLUDED
|
||||||
|
#define DOC_ALGORITHM_FILL_SELECTION_H_INCLUDED
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "doc/color.h"
|
||||||
|
#include "gfx/point.h"
|
||||||
|
|
||||||
|
namespace doc {
|
||||||
|
class Image;
|
||||||
|
class Mask;
|
||||||
|
namespace algorithm {
|
||||||
|
|
||||||
|
void fill_selection(Image* image,
|
||||||
|
const gfx::Point& offset,
|
||||||
|
const Mask* mask,
|
||||||
|
const color_t color);
|
||||||
|
|
||||||
|
} // namespace algorithm
|
||||||
|
} // namespace doc
|
||||||
|
|
||||||
|
#endif
|
96
src/doc/algorithm/modify_selection.cpp
Normal file
96
src/doc/algorithm/modify_selection.cpp
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
// Aseprite Document Library
|
||||||
|
// Copyright (c) 2018 David Capello
|
||||||
|
//
|
||||||
|
// This file is released under the terms of the MIT license.
|
||||||
|
// Read LICENSE.txt for more information.
|
||||||
|
|
||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include "config.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "doc/algorithm/modify_selection.h"
|
||||||
|
|
||||||
|
#include "doc/image_impl.h"
|
||||||
|
#include "doc/mask.h"
|
||||||
|
#include "doc/primitives.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace doc {
|
||||||
|
namespace algorithm {
|
||||||
|
|
||||||
|
// TODO create morphological operators/functions in "doc" namespace
|
||||||
|
// TODO the impl is not optimal, but is good enough as a first version
|
||||||
|
void modify_selection(const SelectionModifier modifier,
|
||||||
|
const Mask* srcMask,
|
||||||
|
Mask* dstMask,
|
||||||
|
const int radius,
|
||||||
|
const doc::BrushType brush)
|
||||||
|
{
|
||||||
|
const doc::Image* srcImage = srcMask->bitmap();
|
||||||
|
doc::Image* dstImage = dstMask->bitmap();
|
||||||
|
const gfx::Point offset =
|
||||||
|
srcMask->bounds().origin() -
|
||||||
|
dstMask->bounds().origin();
|
||||||
|
|
||||||
|
// Image bounds to clip get/put pixels
|
||||||
|
const gfx::Rect srcBounds = srcImage->bounds();
|
||||||
|
|
||||||
|
// Create a kernel
|
||||||
|
const int size = 2*radius+1;
|
||||||
|
std::unique_ptr<doc::Image> kernel(doc::Image::create(IMAGE_BITMAP, size, size));
|
||||||
|
doc::clear_image(kernel.get(), 0);
|
||||||
|
if (brush == doc::kCircleBrushType)
|
||||||
|
doc::fill_ellipse(kernel.get(), 0, 0, size-1, size-1, 1);
|
||||||
|
else
|
||||||
|
doc::fill_rect(kernel.get(), 0, 0, size-1, size-1, 1);
|
||||||
|
doc::put_pixel(kernel.get(), radius, radius, 0);
|
||||||
|
|
||||||
|
int total = 0; // Number of 1s in the kernel image
|
||||||
|
for (int v=0; v<size; ++v)
|
||||||
|
for (int u=0; u<size; ++u)
|
||||||
|
total += kernel->getPixel(u, v);
|
||||||
|
|
||||||
|
for (int y=-radius; y<srcBounds.h+radius; ++y) {
|
||||||
|
for (int x=-radius; x<srcBounds.w+radius; ++x) {
|
||||||
|
doc::color_t c;
|
||||||
|
if (srcBounds.contains(x, y))
|
||||||
|
c = srcImage->getPixel(x, y);
|
||||||
|
else
|
||||||
|
c = 0;
|
||||||
|
|
||||||
|
int accum = 0;
|
||||||
|
for (int v=0; v<size; ++v) {
|
||||||
|
for (int u=0; u<size; ++u) {
|
||||||
|
if (kernel->getPixel(u, v)) {
|
||||||
|
if (srcBounds.contains(x+u-radius, y+v-radius))
|
||||||
|
accum += srcImage->getPixel(x-radius+u, y-radius+v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (modifier) {
|
||||||
|
case SelectionModifier::Border: {
|
||||||
|
c = (c && accum < total) ? 1: 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SelectionModifier::Expand: {
|
||||||
|
c = (c || accum > 0) ? 1: 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SelectionModifier::Contract: {
|
||||||
|
c = (c && accum == total) ? 1: 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c)
|
||||||
|
doc::put_pixel(dstImage,
|
||||||
|
offset.x+x,
|
||||||
|
offset.y+y, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace algorithm
|
||||||
|
} // namespace app
|
34
src/doc/algorithm/modify_selection.h
Normal file
34
src/doc/algorithm/modify_selection.h
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// Aseprite Document Library
|
||||||
|
// Copyright (c) 2018 David Capello
|
||||||
|
//
|
||||||
|
// This file is released under the terms of the MIT license.
|
||||||
|
// Read LICENSE.txt for more information.
|
||||||
|
|
||||||
|
#ifndef DOC_ALGORITHM_MODIFY_SELECTION_H_INCLUDED
|
||||||
|
#define DOC_ALGORITHM_MODIFY_SELECTION_H_INCLUDED
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "doc/brush_type.h"
|
||||||
|
#include "doc/color.h"
|
||||||
|
#include "gfx/point.h"
|
||||||
|
|
||||||
|
namespace doc {
|
||||||
|
class Mask;
|
||||||
|
namespace algorithm {
|
||||||
|
|
||||||
|
enum class SelectionModifier {
|
||||||
|
Border,
|
||||||
|
Expand,
|
||||||
|
Contract,
|
||||||
|
};
|
||||||
|
|
||||||
|
void modify_selection(const SelectionModifier modifier,
|
||||||
|
const Mask* srcMask,
|
||||||
|
Mask* dstMask,
|
||||||
|
const int radius,
|
||||||
|
const BrushType brush);
|
||||||
|
|
||||||
|
} // namespace algorithm
|
||||||
|
} // namespace doc
|
||||||
|
|
||||||
|
#endif
|
51
src/doc/algorithm/stroke_selection.cpp
Normal file
51
src/doc/algorithm/stroke_selection.cpp
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// Aseprite Document Library
|
||||||
|
// Copyright (c) 2018 David Capello
|
||||||
|
//
|
||||||
|
// This file is released under the terms of the MIT license.
|
||||||
|
// Read LICENSE.txt for more information.
|
||||||
|
|
||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include "config.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "doc/algorithm/stroke_selection.h"
|
||||||
|
|
||||||
|
#include "doc/algorithm/fill_selection.h"
|
||||||
|
#include "doc/algorithm/modify_selection.h"
|
||||||
|
#include "doc/mask.h"
|
||||||
|
|
||||||
|
namespace doc {
|
||||||
|
namespace algorithm {
|
||||||
|
|
||||||
|
void stroke_selection(Image* image,
|
||||||
|
const gfx::Point& offset,
|
||||||
|
const Mask* origMask,
|
||||||
|
const color_t color)
|
||||||
|
{
|
||||||
|
ASSERT(origMask);
|
||||||
|
ASSERT(origMask->bitmap());
|
||||||
|
if (!origMask || !origMask->bitmap())
|
||||||
|
return;
|
||||||
|
|
||||||
|
gfx::Rect bounds = origMask->bounds();
|
||||||
|
if (bounds.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
Mask mask;
|
||||||
|
mask.reserve(bounds);
|
||||||
|
mask.freeze();
|
||||||
|
modify_selection(
|
||||||
|
SelectionModifier::Border,
|
||||||
|
origMask, &mask, 1,
|
||||||
|
BrushType::kCircleBrushType);
|
||||||
|
mask.unfreeze();
|
||||||
|
|
||||||
|
// Both mask must have the same bounds.
|
||||||
|
ASSERT(mask.bounds() == origMask->bounds());
|
||||||
|
|
||||||
|
if (mask.bitmap())
|
||||||
|
fill_selection(image, offset, &mask, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace algorithm
|
||||||
|
} // namespace app
|
27
src/doc/algorithm/stroke_selection.h
Normal file
27
src/doc/algorithm/stroke_selection.h
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// Aseprite Document Library
|
||||||
|
// Copyright (c) 2018 David Capello
|
||||||
|
//
|
||||||
|
// This file is released under the terms of the MIT license.
|
||||||
|
// Read LICENSE.txt for more information.
|
||||||
|
|
||||||
|
#ifndef DOC_ALGORITHM_STROKE_SELECTION_H_INCLUDED
|
||||||
|
#define DOC_ALGORITHM_STROKE_SELECTION_H_INCLUDED
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "doc/color.h"
|
||||||
|
#include "gfx/point.h"
|
||||||
|
|
||||||
|
namespace doc {
|
||||||
|
class Image;
|
||||||
|
class Mask;
|
||||||
|
namespace algorithm {
|
||||||
|
|
||||||
|
void stroke_selection(Image* image,
|
||||||
|
const gfx::Point& offset,
|
||||||
|
const Mask* mask,
|
||||||
|
const color_t color);
|
||||||
|
|
||||||
|
} // namespace algorithm
|
||||||
|
} // namespace doc
|
||||||
|
|
||||||
|
#endif
|
@ -20,6 +20,8 @@
|
|||||||
namespace doc {
|
namespace doc {
|
||||||
|
|
||||||
// Represents the selection (selected pixels, 0/1, 0=non-selected, 1=selected)
|
// Represents the selection (selected pixels, 0/1, 0=non-selected, 1=selected)
|
||||||
|
//
|
||||||
|
// TODO rename Mask -> Selection
|
||||||
class Mask : public Object {
|
class Mask : public Object {
|
||||||
public:
|
public:
|
||||||
Mask();
|
Mask();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user