Merge branch 'brush'

This commit is contained in:
David Capello 2015-04-30 12:00:49 -03:00
commit 88a0e8c806
65 changed files with 2067 additions and 392 deletions

View File

@ -149,6 +149,44 @@
<param name="change" value="decrement-size" />
</key>
<!-- Custom brushes -->
<key command="ChangeBrush" shortcut="Alt+1">
<param name="change" value="custom" />
<param name="slot" value="1" />
</key>
<key command="ChangeBrush" shortcut="Alt+2">
<param name="change" value="custom" />
<param name="slot" value="2" />
</key>
<key command="ChangeBrush" shortcut="Alt+3">
<param name="change" value="custom" />
<param name="slot" value="3" />
</key>
<key command="ChangeBrush" shortcut="Alt+4">
<param name="change" value="custom" />
<param name="slot" value="4" />
</key>
<key command="ChangeBrush" shortcut="Alt+5">
<param name="change" value="custom" />
<param name="slot" value="5" />
</key>
<key command="ChangeBrush" shortcut="Alt+6">
<param name="change" value="custom" />
<param name="slot" value="6" />
</key>
<key command="ChangeBrush" shortcut="Alt+7">
<param name="change" value="custom" />
<param name="slot" value="7" />
</key>
<key command="ChangeBrush" shortcut="Alt+8">
<param name="change" value="custom" />
<param name="slot" value="8" />
</key>
<key command="ChangeBrush" shortcut="Alt+9">
<param name="change" value="custom" />
<param name="slot" value="9" />
</key>
<!-- Zoom -->
<key command="Zoom" shortcut="~"><param name="percentage" value="50" /></key>
<key command="Zoom" shortcut="1"><param name="percentage" value="100" /></key>
@ -309,6 +347,7 @@
<param name="quantity" value="1" />
</key>
<key command="NewBrush" shortcut="Ctrl+B" mac="Cmd+B" />
</commands>
<!-- Keyboard shortcuts to select tools -->
@ -438,6 +477,7 @@
<param name="orientation" value="vertical" />
</item>
<item command="MaskContent" text="Transfor&amp;m" />
<item command="NewBrush" text="New &amp;Brush" />
<separator />
<item command="ReplaceColor" text="R&amp;eplace Color..." />
<item command="InvertColor" text="&amp;Invert" />

View File

@ -98,6 +98,9 @@
<option id="new_version" type="std::string" />
<option id="new_url" type="std::string" />
</section>
<section id="brush">
<option id="pattern" type="doc::BrushPattern" default="doc::BrushPattern::DEFAULT" />
</section>
</global>
<tool>
@ -109,9 +112,9 @@
<option id="ink" type="InkType" />
<option id="freehand_algorithm" type="FreehandAlgorithm" />
<section id="brush">
<option id="type" type="BrushType" />
<option id="size" type="int" />
<option id="angle" type="int" />
<option id="type" type="BrushType" default="BrushType::CIRCLE" />
<option id="size" type="int" default="1" />
<option id="angle" type="int" default="0" />
</section>
<section id="spray">
<option id="width" type="int" default="16" />

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -71,6 +71,7 @@ add_library(app-lib
cmd/clear_cel.cpp
cmd/clear_image.cpp
cmd/clear_mask.cpp
cmd/clear_rect.cpp
cmd/configure_background.cpp
cmd/copy_cel.cpp
cmd/copy_frame.cpp
@ -142,6 +143,7 @@ add_library(app-lib
commands/cmd_cut.cpp
commands/cmd_deselect_mask.cpp
commands/cmd_developer_console.cpp
commands/cmd_discard_brush.cpp
commands/cmd_duplicate_layer.cpp
commands/cmd_duplicate_sprite.cpp
commands/cmd_duplicate_view.cpp
@ -173,6 +175,7 @@ add_library(app-lib
commands/cmd_merge_down_layer.cpp
commands/cmd_move_cel.cpp
commands/cmd_move_mask.cpp
commands/cmd_new_brush.cpp
commands/cmd_new_file.cpp
commands/cmd_new_frame.cpp
commands/cmd_new_frame_tag.cpp
@ -288,6 +291,7 @@ add_library(app-lib
transaction.cpp
ui/ani_controls.cpp
ui/app_menuitem.cpp
ui/brush_popup.cpp
ui/button_set.cpp
ui/color_bar.cpp
ui/color_button.cpp

View File

@ -261,7 +261,6 @@ Widget* AppMenus::createInvalidVersionMenuitem()
{
AppMenuItem* menuitem = new AppMenuItem("WARNING!");
Menu* subMenu = new Menu();
Params params;
subMenu->addChild(new AppMenuItem(PACKAGE " is using a customized gui.xml (maybe from your HOME directory)."));
subMenu->addChild(new AppMenuItem("You should update your customized gui.xml file to the new version to get"));
subMenu->addChild(new AppMenuItem("the latest commands available."));

View File

@ -0,0 +1,87 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/cmd/clear_rect.h"
#include "app/document.h"
#include "doc/cel.h"
#include "doc/image.h"
#include "doc/layer.h"
#include "doc/primitives.h"
namespace app {
namespace cmd {
using namespace doc;
ClearRect::ClearRect(Cel* cel, const gfx::Rect& bounds)
{
app::Document* doc = static_cast<app::Document*>(cel->document());
Image* image = (cel ? cel->image(): NULL);
if (!image)
return;
m_offsetX = bounds.x - cel->x();
m_offsetY = bounds.y - cel->y();
gfx::Rect bounds2 =
image->bounds().createIntersect(
gfx::Rect(
m_offsetX, m_offsetY,
bounds.w, bounds.h));
if (bounds.isEmpty())
return;
m_dstImage.reset(new WithImage(image));
m_bgcolor = doc->bgColor(cel->layer());
m_copy.reset(crop_image(image,
bounds2.x, bounds2.y, bounds2.w, bounds2.h, m_bgcolor));
}
void ClearRect::onExecute()
{
m_seq.execute(context());
if (m_dstImage)
clear();
}
void ClearRect::onUndo()
{
if (m_dstImage)
restore();
m_seq.undo();
}
void ClearRect::onRedo()
{
m_seq.redo();
if (m_dstImage)
clear();
}
void ClearRect::clear()
{
fill_rect(m_dstImage->image(),
m_offsetX, m_offsetY,
m_offsetX + m_copy->width() - 1,
m_offsetY + m_copy->height() - 1,
m_bgcolor);
}
void ClearRect::restore()
{
copy_image(m_dstImage->image(), m_copy.get(), m_offsetX, m_offsetY);
}
} // namespace cmd
} // namespace app

54
src/app/cmd/clear_rect.h Normal file
View File

@ -0,0 +1,54 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
#ifndef APP_CMD_CLEAR_RECT_H_INCLUDED
#define APP_CMD_CLEAR_RECT_H_INCLUDED
#pragma once
#include "app/cmd.h"
#include "app/cmd/with_image.h"
#include "app/cmd_sequence.h"
#include "base/unique_ptr.h"
#include "doc/image_ref.h"
#include "gfx/fwd.h"
namespace doc {
class Cel;
}
namespace app {
namespace cmd {
using namespace doc;
class ClearRect : public Cmd {
public:
ClearRect(Cel* cel, const gfx::Rect& bounds);
protected:
void onExecute() override;
void onUndo() override;
void onRedo() override;
size_t onMemSize() const override {
return sizeof(*this) + m_seq.memSize() +
(m_copy ? m_copy->getMemSize(): 0);
}
private:
void clear();
void restore();
CmdSequence m_seq;
base::UniquePtr<WithImage> m_dstImage;
ImageRef m_copy;
int m_offsetX, m_offsetY;
color_t m_bgcolor;
};
} // namespace cmd
} // namespace app
#endif

View File

@ -14,6 +14,7 @@
#include "app/commands/commands.h"
#include "app/commands/params.h"
#include "app/context.h"
#include "ui/manager.h"
namespace app {
@ -59,11 +60,20 @@ void CancelCommand::onExecute(Context* context)
break;
case All:
// Discard brush
{
Command* discardBrush = CommandsModule::instance()->getCommandByName(CommandId::DiscardBrush);
context->executeCommand(discardBrush);
}
// Deselect mask
if (context->checkFlags(ContextFlags::ActiveDocumentIsWritable |
ContextFlags::HasVisibleMask)) {
Command* cmd = CommandsModule::instance()->getCommandByName(CommandId::DeselectMask);
context->executeCommand(cmd);
Command* deselectMask = CommandsModule::instance()->getCommandByName(CommandId::DeselectMask);
context->executeCommand(deselectMask);
}
ui::Manager::getDefault()->invalidate();
break;
}
}

View File

@ -53,8 +53,8 @@ public:
: m_editor(current_editor)
, m_rect(0, 0, current_editor->sprite()->width(), current_editor->sprite()->height())
, m_selectBoxState(new SelectBoxState(this, m_rect,
SelectBoxState::PaintRulers |
SelectBoxState::PaintDarkOutside)) {
SelectBoxState::RULERS |
SelectBoxState::DARKOUTSIDE)) {
setWidth(m_rect.w);
setHeight(m_rect.h);
setLeft(0);
@ -92,7 +92,7 @@ public:
protected:
// SelectBoxDelegate impleentation
virtual void onChangeRectangle(const gfx::Rect& rect) override {
void onChangeRectangle(const gfx::Rect& rect) override {
m_rect = rect;
updateSizeFromRect();

View File

@ -17,6 +17,9 @@
#include "app/context.h"
#include "app/settings/settings.h"
#include "app/tools/tool.h"
#include "app/ui/context_bar.h"
#include "app/ui/main_window.h"
#include "base/convert_to.h"
#include "doc/brush.h"
namespace app {
@ -28,10 +31,9 @@ class ChangeBrushCommand : public Command {
DecrementSize,
IncrementAngle,
DecrementAngle,
CustomBrush,
};
Change m_change;
public:
ChangeBrushCommand();
@ -39,6 +41,10 @@ protected:
void onLoadParams(const Params& params) override;
void onExecute(Context* context) override;
std::string onGetFriendlyName() const override;
private:
Change m_change;
int m_slot;
};
ChangeBrushCommand::ChangeBrushCommand()
@ -47,6 +53,7 @@ ChangeBrushCommand::ChangeBrushCommand()
CmdUIOnlyFlag)
{
m_change = None;
m_slot = 0;
}
void ChangeBrushCommand::onLoadParams(const Params& params)
@ -56,6 +63,12 @@ void ChangeBrushCommand::onLoadParams(const Params& params)
else if (change == "decrement-size") m_change = DecrementSize;
else if (change == "increment-angle") m_change = IncrementAngle;
else if (change == "decrement-angle") m_change = DecrementAngle;
else if (change == "custom") m_change = CustomBrush;
if (m_change == CustomBrush)
m_slot = params.get_as<int>("slot");
else
m_slot = 0;
}
void ChangeBrushCommand::onExecute(Context* context)
@ -84,6 +97,10 @@ void ChangeBrushCommand::onExecute(Context* context)
if (brush->getAngle() > 0)
brush->setAngle(brush->getAngle()-1);
break;
case CustomBrush:
App::instance()->getMainWindow()->getContextBar()
->setActiveBrushBySlot(m_slot);
break;
}
}
@ -106,6 +123,10 @@ std::string ChangeBrushCommand::onGetFriendlyName() const
case DecrementAngle:
text += ": Decrement Angle";
break;
case CustomBrush:
text += ": Custom Brush #";
text += base::convert_to<std::string>(m_slot);
break;
}
return text;

View File

@ -0,0 +1,59 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/app.h"
#include "app/commands/command.h"
#include "app/commands/commands.h"
#include "app/context_access.h"
#include "app/settings/settings.h"
#include "app/tools/tool_box.h"
#include "app/ui/context_bar.h"
#include "app/ui/main_window.h"
#include "app/ui_context.h"
#include "app/util/new_image_from_mask.h"
namespace app {
class DiscardBrushCommand : public Command {
public:
DiscardBrushCommand();
Command* clone() const override { return new DiscardBrushCommand(*this); }
protected:
bool onEnabled(Context* context) override;
void onExecute(Context* context) override;
};
DiscardBrushCommand::DiscardBrushCommand()
: Command("DiscardBrush",
"Discard Brush",
CmdUIOnlyFlag)
{
}
bool DiscardBrushCommand::onEnabled(Context* context)
{
ContextBar* ctxBar = App::instance()->getMainWindow()->getContextBar();
return (ctxBar->activeBrush()->type() == kImageBrushType);
}
void DiscardBrushCommand::onExecute(Context* context)
{
ContextBar* ctxBar = App::instance()->getMainWindow()->getContextBar();
ctxBar->discardActiveBrush();
}
Command* CommandFactory::createDiscardBrushCommand()
{
return new DiscardBrushCommand();
}
} // namespace app

View File

@ -13,6 +13,7 @@
#include "app/color.h"
#include "app/color_picker.h"
#include "app/commands/command.h"
#include "app/commands/commands.h"
#include "app/commands/params.h"
#include "app/modules/editors.h"
#include "app/settings/settings.h"
@ -72,7 +73,13 @@ void EyedropperCommand::onExecute(Context* context)
if (!sprite)
return;
// pixel position to get
// Discard current image brush
{
Command* discardBrush = CommandsModule::instance()->getCommandByName(CommandId::DiscardBrush);
context->executeCommand(discardBrush);
}
// Pixel position to get
gfx::Point pixelPos = editor->screenToEditor(ui::get_mouse_position());
// Check if we've to grab alpha channel or the merged color.

View File

@ -192,8 +192,8 @@ private:
m_editor = current_editor;
EditorStatePtr newState(new SelectBoxState(this, m_rect,
SelectBoxState::PaintRulers |
SelectBoxState::PaintGrid));
SelectBoxState::RULERS |
SelectBoxState::GRID));
m_editor->setState(newState);
}
}

View File

@ -0,0 +1,170 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/app.h"
#include "app/cmd/clear_rect.h"
#include "app/commands/command.h"
#include "app/commands/commands.h"
#include "app/console.h"
#include "app/context_access.h"
#include "app/modules/editors.h"
#include "app/settings/settings.h"
#include "app/tools/tool_box.h"
#include "app/transaction.h"
#include "app/ui/context_bar.h"
#include "app/ui/editor/editor.h"
#include "app/ui/editor/select_box_state.h"
#include "app/ui/keyboard_shortcuts.h"
#include "app/ui/main_window.h"
#include "app/ui/status_bar.h"
#include "app/ui_context.h"
#include "app/util/new_image_from_mask.h"
#include "base/convert_to.h"
#include "doc/mask.h"
namespace app {
class NewBrushCommand : public Command
, public SelectBoxDelegate {
public:
NewBrushCommand();
Command* clone() const override { return new NewBrushCommand(*this); }
protected:
bool onEnabled(Context* context) override;
void onExecute(Context* context) override;
// SelectBoxDelegate impl
void onQuickboxEnd(const gfx::Rect& rect, ui::MouseButtons buttons) override;
void onQuickboxCancel() override;
private:
void createBrush(const Mask* mask);
};
NewBrushCommand::NewBrushCommand()
: Command("NewBrush",
"New Brush",
CmdUIOnlyFlag)
{
}
bool NewBrushCommand::onEnabled(Context* context)
{
return context->checkFlags(ContextFlags::ActiveDocumentIsWritable);
}
void NewBrushCommand::onExecute(Context* context)
{
// If there is no visible mask, the brush must be selected from the
// current editor.
if (!context->activeDocument()->isMaskVisible()) {
EditorStatePtr state = current_editor->getState();
if (dynamic_cast<SelectBoxState*>(state.get())) {
// If already are in "SelectBoxState" state, in this way we
// avoid creating a stack of several "SelectBoxState" states.
return;
}
current_editor->setState(
EditorStatePtr(
new SelectBoxState(
this, current_editor->sprite()->bounds(),
SelectBoxState::DARKOUTSIDE |
SelectBoxState::QUICKBOX)));
}
// Create a brush from the active selection
else {
createBrush(context->activeDocument()->mask());
// Set pencil as current tool
ISettings* settings = UIContext::instance()->settings();
tools::Tool* pencil =
App::instance()->getToolBox()->getToolById(tools::WellKnownTools::Pencil);
settings->setCurrentTool(pencil);
// Deselect mask
Command* cmd =
CommandsModule::instance()->getCommandByName(CommandId::DeselectMask);
UIContext::instance()->executeCommand(cmd);
}
}
void NewBrushCommand::onQuickboxEnd(const gfx::Rect& rect, ui::MouseButtons buttons)
{
Mask mask;
mask.replace(rect);
createBrush(&mask);
// If the right-button was used, we clear the selected area.
if (buttons & ui::kButtonRight) {
try {
ContextWriter writer(UIContext::instance(), 250);
Transaction transaction(writer.context(), "Clear");
transaction.execute(new cmd::ClearRect(writer.cel(), rect));
transaction.commit();
}
catch (const std::exception& ex) {
Console::showException(ex);
}
}
// Update the context bar
// TODO find a way to avoid all these singletons. Maybe a simple
// signal in the context like "brush has changed" could be enough.
App::instance()->getMainWindow()->getContextBar()
->updateFromTool(UIContext::instance()->settings()->getCurrentTool());
current_editor->backToPreviousState();
}
void NewBrushCommand::onQuickboxCancel()
{
current_editor->backToPreviousState();
}
void NewBrushCommand::createBrush(const Mask* mask)
{
doc::ImageRef image(new_image_from_mask(
UIContext::instance()->activeSite(), mask));
if (!image)
return;
// New brush
doc::BrushRef brush(new doc::Brush());
brush->setImage(image.get());
brush->setPatternOrigin(mask->bounds().getOrigin());
// TODO add a active stock property in app::Context
ContextBar* ctxBar = App::instance()->getMainWindow()->getContextBar();
int slot = ctxBar->addBrush(brush);
ctxBar->setActiveBrush(brush);
// Get the shortcut for this brush and show it to the user
Params params;
params.set("change", "custom");
params.set("slot", base::convert_to<std::string>(slot).c_str());
Key* key = KeyboardShortcuts::instance()->command(
CommandId::ChangeBrush, params);
if (key && !key->accels().empty()) {
std::string tooltip;
tooltip += "Shortcut: ";
tooltip += key->accels().front().toString();
StatusBar::instance()->showTip(2000, tooltip.c_str());
}
}
Command* CommandFactory::createNewBrushCommand()
{
return new NewBrushCommand();
}
} // namespace app

View File

@ -29,6 +29,7 @@ FOR_EACH_COMMAND(Cut)
FOR_EACH_COMMAND(DeselectMask)
FOR_EACH_COMMAND(Despeckle)
FOR_EACH_COMMAND(DeveloperConsole)
FOR_EACH_COMMAND(DiscardBrush)
FOR_EACH_COMMAND(DuplicateLayer)
FOR_EACH_COMMAND(DuplicateSprite)
FOR_EACH_COMMAND(DuplicateView)
@ -67,6 +68,7 @@ FOR_EACH_COMMAND(MaskContent)
FOR_EACH_COMMAND(MergeDownLayer)
FOR_EACH_COMMAND(MoveCel)
FOR_EACH_COMMAND(MoveMask)
FOR_EACH_COMMAND(NewBrush)
FOR_EACH_COMMAND(NewFile)
FOR_EACH_COMMAND(NewFrame)
FOR_EACH_COMMAND(NewFrameTag)

View File

@ -54,6 +54,7 @@ Document::Document(Sprite* sprite)
, m_extraCel(NULL)
, m_extraImage(NULL)
, m_extraCelBlendMode(BLEND_MODE_NORMAL)
, m_extraCelType(render::ExtraType::NONE)
// Mask
, m_mask(new Mask())
, m_maskVisible(true)
@ -272,6 +273,7 @@ void Document::destroyExtraCel()
m_extraCel = NULL;
m_extraImage.reset(NULL);
m_extraCelType = render::ExtraType::NONE;
}
void Document::prepareExtraCel(const gfx::Rect& bounds, int opacity)
@ -293,6 +295,11 @@ void Document::prepareExtraCel(const gfx::Rect& bounds, int opacity)
m_extraCel->setOpacity(opacity);
}
void Document::setExtraCelType(render::ExtraType type)
{
m_extraCelType = type;
}
Cel* Document::getExtraCel() const
{
return m_extraCel;

View File

@ -21,6 +21,7 @@
#include "doc/pixel_format.h"
#include "gfx/rect.h"
#include "gfx/transformation.h"
#include "render/extra_type.h"
#include <string>
@ -123,10 +124,11 @@ namespace app {
// Extra Cel (it is used to draw pen preview, pixels in movement, etc.)
void prepareExtraCel(const gfx::Rect& bounds, int opacity);
void setExtraCelType(render::ExtraType type);
void destroyExtraCel();
Cel* getExtraCel() const;
Image* getExtraCelImage() const;
render::ExtraType getExtraCelType() const { return m_extraCelType; }
int getExtraCelBlendMode() const { return m_extraCelBlendMode; }
void setExtraCelBlendMode(int mode) { m_extraCelBlendMode = mode; }
@ -216,6 +218,7 @@ namespace app {
// Image of the extra cel.
ImageRef m_extraImage;
int m_extraCelBlendMode;
render::ExtraType m_extraCelType;
// Current mask.
base::UniquePtr<Mask> m_mask;

View File

@ -12,10 +12,6 @@
#include "app/pref/option.h"
#include "doc/documents_observer.h"
namespace filters {
enum class TiledMode;
}
#include "generated_pref_types.h"
#include <map>

View File

@ -9,6 +9,7 @@
#include "app/tools/shade_table.h"
#include "app/tools/shading_options.h"
#include "doc/palette.h"
#include "doc/primitives_fast.h"
#include "doc/rgbmap.h"
#include "doc/sprite.h"
#include "filters/neighboring_pixels.h"
@ -699,6 +700,150 @@ private:
color_t m_color;
};
//////////////////////////////////////////////////////////////////////
// Brush Ink
//////////////////////////////////////////////////////////////////////
template<typename ImageTraits>
class BrushInkProcessing : public DoubleInkProcessing<BrushInkProcessing<ImageTraits>, ImageTraits> {
public:
BrushInkProcessing(ToolLoop* loop) {
m_fgColor = loop->getPrimaryColor();
m_bgColor = loop->getSecondaryColor();
m_palette = get_current_palette();
m_brush = loop->getBrush();
m_brushImage = m_brush->image();
m_opacity = loop->getOpacity();
m_width = m_brush->bounds().w;
m_height = m_brush->bounds().h;
m_u = (loop->getOffset().x + m_brush->patternOrigin().x) % m_width;
m_v = (loop->getOffset().y + m_brush->patternOrigin().y) % m_height;
}
void processPixel(int x, int y) {
// Do nothing
}
private:
void alignPixelPoint(int& x, int& y) {
x = (x - m_u) % m_width;
y = (y - m_v) % m_height;
if (x < 0) x = m_width - ((-x) % m_width);
if (y < 0) y = m_height - ((-y) % m_height);
}
color_t m_fgColor;
color_t m_bgColor;
const Palette* m_palette;
const Brush* m_brush;
const Image* m_brushImage;
int m_opacity;
int m_u, m_v, m_width, m_height;
};
template<>
void BrushInkProcessing<RgbTraits>::processPixel(int x, int y) {
alignPixelPoint(x, y);
color_t c;
switch (m_brushImage->pixelFormat()) {
case IMAGE_RGB: {
c = get_pixel_fast<RgbTraits>(m_brushImage, x, y);
break;
}
case IMAGE_INDEXED: {
c = get_pixel_fast<IndexedTraits>(m_brushImage, x, y);
c = m_palette->getEntry(c);
break;
}
case IMAGE_GRAYSCALE: {
c = get_pixel_fast<GrayscaleTraits>(m_brushImage, x, y);
c = graya(m_palette->getEntry(c), graya_geta(c));
break;
}
case IMAGE_BITMAP: {
c = get_pixel_fast<BitmapTraits>(m_brushImage, x, y);
c = c ? m_fgColor: m_bgColor;
break;
}
default:
ASSERT(false);
return;
}
*m_dstAddress = rgba_blend_normal(*m_srcAddress, c, m_opacity);
}
template<>
void BrushInkProcessing<GrayscaleTraits>::processPixel(int x, int y) {
alignPixelPoint(x, y);
color_t c;
switch (m_brushImage->pixelFormat()) {
case IMAGE_RGB: {
c = get_pixel_fast<RgbTraits>(m_brushImage, x, y);
c = graya(int(rgba_getr(c)) + int(rgba_getg(c)) + int(rgba_getb(c)) / 3,
rgba_geta(c));
break;
}
case IMAGE_INDEXED: {
c = get_pixel_fast<IndexedTraits>(m_brushImage, x, y);
c = m_palette->getEntry(c);
c = graya(int(rgba_getr(c)) + int(rgba_getg(c)) + int(rgba_getb(c)) / 3,
rgba_geta(c));
break;
}
case IMAGE_GRAYSCALE: {
c = get_pixel_fast<GrayscaleTraits>(m_brushImage, x, y);
break;
}
case IMAGE_BITMAP: {
c = get_pixel_fast<BitmapTraits>(m_brushImage, x, y);
c = c ? m_fgColor: m_bgColor;
break;
}
default:
ASSERT(false);
return;
}
*m_dstAddress = graya_blend_normal(*m_srcAddress, c, m_opacity);
}
template<>
void BrushInkProcessing<IndexedTraits>::processPixel(int x, int y) {
alignPixelPoint(x, y);
color_t c;
switch (m_brushImage->pixelFormat()) {
case IMAGE_RGB: {
c = get_pixel_fast<RgbTraits>(m_brushImage, x, y);
c = m_palette->findBestfit(rgba_getr(c), rgba_getg(c), rgba_getb(c));
break;
}
case IMAGE_INDEXED: {
c = get_pixel_fast<IndexedTraits>(m_brushImage, x, y);
break;
}
case IMAGE_GRAYSCALE: {
c = get_pixel_fast<GrayscaleTraits>(m_brushImage, x, y);
c = graya_getv(c);
c = m_palette->findBestfit(c, c, c);
break;
}
case IMAGE_BITMAP: {
c = get_pixel_fast<BitmapTraits>(m_brushImage, x, y);
c = c ? m_fgColor: m_bgColor;
break;
}
default:
ASSERT(false);
return;
}
*m_dstAddress = c;
}
//////////////////////////////////////////////////////////////////////
enum {
@ -711,6 +856,7 @@ enum {
INK_JUMBLE,
INK_SHADING,
INK_XOR,
INK_BRUSH,
MAX_INKS
};
@ -737,7 +883,8 @@ AlgoHLine ink_processing[][3] =
DEFINE_INK(ReplaceInkProcessing),
DEFINE_INK(JumbleInkProcessing),
DEFINE_INK(ShadingInkProcessing),
DEFINE_INK(XorInkProcessing)
DEFINE_INK(XorInkProcessing),
DEFINE_INK(BrushInkProcessing)
};
} // anonymous namespace

View File

@ -60,7 +60,10 @@ public:
switch (m_type) {
case Opaque:
m_proc = ink_processing[INK_OPAQUE][depth];
if (loop->getBrush()->type() == doc::kImageBrushType)
m_proc = ink_processing[INK_BRUSH][depth];
else
m_proc = ink_processing[INK_OPAQUE][depth];
break;
case SetAlpha:
m_proc = ink_processing[INK_SETALPHA][depth];
@ -69,9 +72,12 @@ public:
m_proc = ink_processing[INK_LOCKALPHA][depth];
break;
default:
m_proc = (loop->getOpacity() == 255 ?
ink_processing[INK_OPAQUE][depth]:
ink_processing[INK_TRANSPARENT][depth]);
if (loop->getBrush()->type() == doc::kImageBrushType)
m_proc = ink_processing[INK_BRUSH][depth];
else
m_proc = (loop->getOpacity() == 255 ?
ink_processing[INK_OPAQUE][depth]:
ink_processing[INK_TRANSPARENT][depth]);
break;
}
}

View File

@ -21,6 +21,7 @@ namespace app {
virtual ~PointShape() { }
virtual bool isFloodFill() { return false; }
virtual bool isSpray() { return false; }
virtual void preparePointShape(ToolLoop* loop) { }
virtual void transformPoint(ToolLoop* loop, int x, int y) = 0;
virtual void getModifiedArea(ToolLoop* loop, int x, int y, gfx::Rect& area) = 0;

View File

@ -10,60 +10,80 @@ namespace tools {
class NonePointShape : public PointShape {
public:
void transformPoint(ToolLoop* loop, int x, int y)
{
void transformPoint(ToolLoop* loop, int x, int y) override {
// Do nothing
}
void getModifiedArea(ToolLoop* loop, int x, int y, Rect& area)
{
void getModifiedArea(ToolLoop* loop, int x, int y, Rect& area) override {
// Do nothing
}
};
class PixelPointShape : public PointShape {
public:
void transformPoint(ToolLoop* loop, int x, int y)
{
void transformPoint(ToolLoop* loop, int x, int y) override {
doInkHline(x, y, x, loop);
}
void getModifiedArea(ToolLoop* loop, int x, int y, Rect& area)
{
void getModifiedArea(ToolLoop* loop, int x, int y, Rect& area) override {
area = Rect(x, y, 1, 1);
}
};
class BrushPointShape : public PointShape {
Brush* m_brush;
base::SharedPtr<CompressedImage> m_compressedImage;
bool m_firstPoint;
public:
void transformPoint(ToolLoop* loop, int x, int y)
{
Brush* brush = loop->getBrush();
std::vector<BrushScanline>::const_iterator scanline = brush->scanline().begin();
int v, h = brush->bounds().h;
x += brush->bounds().x;
y += brush->bounds().y;
void preparePointShape(ToolLoop* loop) override {
m_brush = loop->getBrush();
m_compressedImage.reset(new CompressedImage(m_brush->image(), false));
m_firstPoint = true;
}
for (v=0; v<h; ++v) {
if (scanline->state)
doInkHline(x+scanline->x1, y+v, x+scanline->x2, loop);
++scanline;
void transformPoint(ToolLoop* loop, int x, int y) override {
int h = m_brush->bounds().h;
x += m_brush->bounds().x;
y += m_brush->bounds().y;
if (m_firstPoint) {
m_firstPoint = false;
if (m_brush->type() == kImageBrushType) {
if (m_brush->pattern() == BrushPattern::ALIGNED_TO_DST ||
m_brush->pattern() == BrushPattern::PAINT_BRUSH) {
m_brush->setPatternOrigin(gfx::Point(x, y)-loop->getOffset());
}
}
}
else {
if (m_brush->type() == kImageBrushType &&
m_brush->pattern() == BrushPattern::PAINT_BRUSH) {
m_brush->setPatternOrigin(gfx::Point(x, y)-loop->getOffset());
}
}
for (auto scanline : *m_compressedImage) {
int u = x+scanline.x;
doInkHline(u, y+scanline.y, u+scanline.w-1, loop);
}
}
void getModifiedArea(ToolLoop* loop, int x, int y, Rect& area)
{
Brush* brush = loop->getBrush();
area = brush->bounds();
void getModifiedArea(ToolLoop* loop, int x, int y, Rect& area) override {
area = m_brush->bounds();
area.x += x;
area.y += y;
}
};
class FloodFillPointShape : public PointShape {
public:
bool isFloodFill() { return true; }
bool isFloodFill() override { return true; }
void transformPoint(ToolLoop* loop, int x, int y)
{
void transformPoint(ToolLoop* loop, int x, int y) override {
doc::algorithm::floodfill(
const_cast<Image*>(loop->getSrcImage()), x, y,
paintBounds(loop, x, y),
@ -72,8 +92,7 @@ public:
loop, (AlgoHLine)doInkHline);
}
void getModifiedArea(ToolLoop* loop, int x, int y, Rect& area)
{
void getModifiedArea(ToolLoop* loop, int x, int y, Rect& area) override {
area = paintBounds(loop, x, y);
}
@ -112,10 +131,13 @@ class SprayPointShape : public PointShape {
public:
bool isSpray() { return true; }
bool isSpray() override { return true; }
void transformPoint(ToolLoop* loop, int x, int y)
{
void preparePointShape(ToolLoop* loop) override {
m_subPointShape.preparePointShape(loop);
}
void transformPoint(ToolLoop* loop, int x, int y) override {
int spray_width = loop->getSprayWidth();
int spray_speed = loop->getSpraySpeed();
int c, u, v, times = (spray_width*spray_width/4) * spray_speed / 100;
@ -145,8 +167,7 @@ public:
#endif
}
void getModifiedArea(ToolLoop* loop, int x, int y, Rect& area)
{
void getModifiedArea(ToolLoop* loop, int x, int y, Rect& area) override {
int spray_width = loop->getSprayWidth();
Point p1(x-spray_width, y-spray_width);
Point p2(x+spray_width, y+spray_width);

View File

@ -23,6 +23,7 @@
#include "doc/algorithm/floodfill.h"
#include "doc/algorithm/polygon.h"
#include "doc/brush.h"
#include "doc/compressed_image.h"
#include "doc/image.h"
#include "doc/mask.h"
#include "fixmath/fixmath.h"
@ -40,6 +41,7 @@ namespace tools {
using namespace gfx;
const char* WellKnownTools::RectangularMarquee = "rectangular_marquee";
const char* WellKnownTools::Pencil = "pencil";
const char* WellKnownTools::Eraser = "eraser";
const char* WellKnownTools::Eyedropper = "eyedropper";
@ -70,6 +72,12 @@ const char* WellKnownIntertwiners::AsEllipses = "as_ellipses";
const char* WellKnownIntertwiners::AsBezier = "as_bezier";
const char* WellKnownIntertwiners::AsPixelPerfect = "as_pixel_perfect";
const char* WellKnownPointShapes::None = "none";
const char* WellKnownPointShapes::Pixel = "pixel";
const char* WellKnownPointShapes::Brush = "brush";
const char* WellKnownPointShapes::FloodFill = "floodfill";
const char* WellKnownPointShapes::Spray = "spray";
ToolBox::ToolBox()
{
PRINTF("Toolbox module: installing\n");
@ -100,11 +108,11 @@ ToolBox::ToolBox()
m_controllers["two_points"] = new TwoPointsController();
m_controllers["four_points"] = new FourPointsController();
m_pointshapers["none"] = new NonePointShape();
m_pointshapers["pixel"] = new PixelPointShape();
m_pointshapers["brush"] = new BrushPointShape();
m_pointshapers["floodfill"] = new FloodFillPointShape();
m_pointshapers["spray"] = new SprayPointShape();
m_pointshapers[WellKnownPointShapes::None] = new NonePointShape();
m_pointshapers[WellKnownPointShapes::Pixel] = new PixelPointShape();
m_pointshapers[WellKnownPointShapes::Brush] = new BrushPointShape();
m_pointshapers[WellKnownPointShapes::FloodFill] = new FloodFillPointShape();
m_pointshapers[WellKnownPointShapes::Spray] = new SprayPointShape();
m_intertwiners[WellKnownIntertwiners::None] = new IntertwineNone();
m_intertwiners[WellKnownIntertwiners::AsLines] = new IntertwineAsLines();
@ -162,6 +170,11 @@ Intertwine* ToolBox::getIntertwinerById(const std::string& id)
return m_intertwiners[id];
}
PointShape* ToolBox::getPointShapeById(const std::string& id)
{
return m_pointshapers[id];
}
void ToolBox::loadTools()
{
PRINTF("Loading Aseprite tools\n");

View File

@ -22,6 +22,7 @@ namespace app {
namespace WellKnownTools {
extern const char* RectangularMarquee;
extern const char* Pencil;
extern const char* Eraser;
extern const char* Eyedropper;
};
@ -57,6 +58,14 @@ namespace app {
extern const char* AsPixelPerfect;
};
namespace WellKnownPointShapes {
extern const char* None;
extern const char* Pixel;
extern const char* Brush;
extern const char* FloodFill;
extern const char* Spray;
};
typedef std::list<Tool*> ToolList;
typedef ToolList::iterator ToolIterator;
typedef ToolList::const_iterator ToolConstIterator;
@ -80,6 +89,7 @@ namespace app {
Tool* getToolById(const std::string& id);
Ink* getInkById(const std::string& id);
Intertwine* getIntertwinerById(const std::string& id);
PointShape* getPointShapeById(const std::string& id);
int getGroupsCount() const { return m_groups.size(); }
private:

View File

@ -55,6 +55,7 @@ void ToolLoopManager::prepareLoop(const Pointer& pointer)
m_toolLoop->getInk()->prepareInk(m_toolLoop);
m_toolLoop->getIntertwine()->prepareIntertwine();
m_toolLoop->getController()->prepareController();
m_toolLoop->getPointShape()->preparePointShape(m_toolLoop);
// Prepare preview image (the destination image will be our preview
// in the tool-loop time, so we can see what we are drawing)

232
src/app/ui/brush_popup.cpp Normal file
View File

@ -0,0 +1,232 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/ui/brush_popup.h"
#include "app/commands/commands.h"
#include "app/modules/palettes.h"
#include "app/ui/app_menuitem.h"
#include "app/ui/button_set.h"
#include "app/ui/keyboard_shortcuts.h"
#include "app/ui/skin/skin_theme.h"
#include "base/convert_to.h"
#include "doc/brush.h"
#include "doc/conversion_she.h"
#include "doc/image.h"
#include "doc/palette.h"
#include "gfx/border.h"
#include "gfx/region.h"
#include "she/scoped_surface_lock.h"
#include "she/surface.h"
#include "she/system.h"
#include "ui/menu.h"
#include "ui/message.h"
#include "ui/separator.h"
#include "ui/tooltips.h"
namespace app {
using namespace app::skin;
using namespace doc;
using namespace ui;
class Item : public ButtonSet::Item {
public:
Item(BrushPopup* popup, BrushPopupDelegate* delegate, const BrushRef& brush, int slot = -1)
: m_popup(popup)
, m_delegate(delegate)
, m_brush(brush)
, m_slot(slot) {
setIcon(BrushPopup::createSurfaceForBrush(brush));
}
~Item() {
icon()->dispose();
}
const BrushRef& brush() const {
return m_brush;
}
protected:
bool onProcessMessage(Message* msg) override {
if (msg->type() == kMouseUpMessage && m_slot > 0) {
MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
if (mouseMsg->buttons() == kButtonRight) {
Menu menu;
AppMenuItem deleteItem("Delete");
AppMenuItem deleteAllItem("Delete All");
deleteItem.Click.connect(&Item::onDeleteBrush, this);
deleteAllItem.Click.connect(&Item::onDeleteAllBrushes, this);
menu.addChild(&deleteItem);
menu.addChild(new Separator("", JI_HORIZONTAL));
menu.addChild(&deleteAllItem);
// Here we make the popup window temporaly floating, so it's
// not closed by the popup menu.
m_popup->makeFloating();
menu.showPopup(mouseMsg->position());
m_popup->makeFixed();
m_popup->closeWindow(nullptr);
}
}
return ButtonSet::Item::onProcessMessage(msg);
}
private:
void onDeleteBrush() {
m_delegate->onDeleteBrushSlot(m_slot);
}
void onDeleteAllBrushes() {
m_delegate->onDeleteAllBrushes();
}
BrushPopup* m_popup;
BrushPopupDelegate* m_delegate;
BrushRef m_brush;
int m_slot;
};
static BrushRef defBrushes[3];
BrushPopup::BrushPopup(BrushPopupDelegate* delegate)
: PopupWindow("", kCloseOnClickInOtherWindow)
, m_delegate(delegate)
{
setAutoRemap(false);
setBorder(gfx::Border(0));
child_spacing = 0;
}
void BrushPopup::setBrush(Brush* brush)
{
for (auto child : m_buttons->getChildren()) {
Item* item = static_cast<Item*>(child);
// Same type and same image
if (item->brush() &&
item->brush()->type() == brush->type() &&
(brush->type() != kImageBrushType ||
item->brush()->image() == brush->image())) {
m_buttons->setSelectedItem(item);
break;
}
}
}
void BrushPopup::regenerate(const gfx::Rect& box, const Brushes& brushes)
{
SkinTheme* theme = static_cast<SkinTheme*>(getTheme());
int columns = 3;
if (m_buttons) {
for (auto child : m_buttons->getChildren())
m_tooltipManager->removeTooltipFor(child);
removeChild(m_buttons.get());
m_buttons.reset();
}
if (!defBrushes[0]) {
defBrushes[0].reset(new Brush(kCircleBrushType, 7, 0));
defBrushes[1].reset(new Brush(kSquareBrushType, 7, 0));
defBrushes[2].reset(new Brush(kLineBrushType, 7, 44));
}
m_buttons.reset(new ButtonSet(columns));
m_buttons->addItem(new Item(this, m_delegate, defBrushes[0]));
m_buttons->addItem(new Item(this, m_delegate, defBrushes[1]));
m_buttons->addItem(new Item(this, m_delegate, defBrushes[2]));
int slot = 1;
for (const auto& brush : brushes) {
Item* item = new Item(this, m_delegate, brush, slot);
m_buttons->addItem(item);
Params params;
params.set("change", "custom");
params.set("slot", base::convert_to<std::string>(slot).c_str());
Key* key = KeyboardShortcuts::instance()->command(
CommandId::ChangeBrush, params);
if (key && !key->accels().empty()) {
std::string tooltip;
tooltip += "Shortcut: ";
tooltip += key->accels().front().toString();
m_tooltipManager->addTooltipFor(item, tooltip, JI_TOP);
}
slot++;
}
// Add empty spaces
while (((slot-1) % columns) > 0)
m_buttons->addItem(new Item(this, m_delegate, BrushRef(nullptr), slot++));
m_buttons->ItemChange.connect(&BrushPopup::onButtonChange, this);
m_buttons->setTransparent(true);
m_buttons->setBgColor(gfx::ColorNone);
addChild(m_buttons.get());
gfx::Rect rc = box;
int buttons = m_buttons->getChildren().size();
int rows = (buttons/columns + ((buttons%columns) > 0 ? 1: 0));
rc.w *= columns;
rc.h = rows * (rc.h-2*guiscale()) + 2*guiscale();
setBounds(rc);
}
void BrushPopup::onButtonChange()
{
Item* item = static_cast<Item*>(m_buttons->getItem(m_buttons->selectedItem()));
if (item->brush())
BrushChange(item->brush());
}
// static
she::Surface* BrushPopup::createSurfaceForBrush(const BrushRef& origBrush)
{
Image* image = nullptr;
BrushRef brush = origBrush;
if (brush) {
if (brush->type() != kImageBrushType && brush->size() > 10) {
brush.reset(new Brush(*brush));
brush->setSize(10);
}
image = brush->image();
}
she::Surface* surface = she::instance()->createRgbaSurface(
std::min(10, image ? image->width(): 4),
std::min(10, image ? image->height(): 4));
if (image) {
Palette* palette = get_current_palette();
if (image->pixelFormat() == IMAGE_BITMAP) {
palette = new Palette(frame_t(0), 2);
palette->setEntry(0, rgba(0, 0, 0, 0));
palette->setEntry(1, rgba(0, 0, 0, 255));
}
convert_image_to_surface(
image, palette, surface,
0, 0, 0, 0, image->width(), image->height());
if (image->pixelFormat() == IMAGE_BITMAP)
delete palette;
}
else {
she::ScopedSurfaceLock lock(surface);
lock->clear();
}
return surface;
}
} // namespace app

61
src/app/ui/brush_popup.h Normal file
View File

@ -0,0 +1,61 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
#ifndef APP_UI_BRUSH_POPUP_H_INCLUDED
#define APP_UI_BRUSH_POPUP_H_INCLUDED
#pragma once
#include "base/shared_ptr.h"
#include "base/signal.h"
#include "doc/brushes.h"
#include "ui/popup_window.h"
namespace doc {
class Brush;
}
namespace ui {
class Menu;
class TooltipManager;
}
namespace app {
class ButtonSet;
class BrushPopupDelegate {
public:
virtual ~BrushPopupDelegate() { }
virtual void onDeleteBrushSlot(int slot) = 0;
virtual void onDeleteAllBrushes() = 0;
};
class BrushPopup : public ui::PopupWindow {
public:
BrushPopup(BrushPopupDelegate* delegate);
void setBrush(doc::Brush* brush);
void regenerate(const gfx::Rect& box, const doc::Brushes& brushes);
void setupTooltips(ui::TooltipManager* tooltipManager) {
m_tooltipManager = tooltipManager;
}
Signal1<void, const doc::BrushRef&> BrushChange;
static she::Surface* createSurfaceForBrush(const doc::BrushRef& brush);
private:
void onButtonChange();
base::SharedPtr<ButtonSet> m_buttons;
ui::TooltipManager* m_tooltipManager;
BrushPopupDelegate* m_delegate;
};
} // namespace app
#endif

View File

@ -145,7 +145,7 @@ void ButtonSet::Item::onPreferredSize(ui::PreferredSizeEvent& ev)
Grid::Info info = buttonSet()->getChildInfo(this);
if (info.row == info.grid_rows-1)
sz.h += 3;
sz.h += 3*guiscale();
ev.setPreferredSize(sz*guiscale());
}
@ -162,6 +162,11 @@ void ButtonSet::addItem(she::Surface* icon, int hspan, int vspan)
{
Item* item = new Item();
item->setIcon(icon);
addItem(item, hspan, vspan);
}
void ButtonSet::addItem(Item* item, int hspan, int vspan)
{
addChildInCell(item, hspan, vspan, JI_CENTER | JI_MIDDLE);
}

View File

@ -22,6 +22,7 @@ namespace app {
public:
Item();
void setIcon(she::Surface* icon);
she::Surface* icon() const { return m_icon; }
ButtonSet* buttonSet();
protected:
void onPaint(ui::PaintEvent& ev) override;
@ -34,6 +35,7 @@ namespace app {
ButtonSet(int columns);
void addItem(she::Surface* icon, int hspan = 1, int vspan = 1);
void addItem(Item* item, int hspan = 1, int vspan = 1);
Item* getItem(int index);
int selectedItem() const;

View File

@ -12,7 +12,10 @@
#include "app/ui/context_bar.h"
#include "app/app.h"
#include "app/commands/commands.h"
#include "app/modules/gui.h"
#include "app/modules/palettes.h"
#include "app/pref/preferences.h"
#include "app/settings/ink_type.h"
#include "app/settings/selection_mode.h"
#include "app/settings/settings.h"
@ -22,6 +25,7 @@
#include "app/tools/point_shape.h"
#include "app/tools/tool.h"
#include "app/tools/tool_box.h"
#include "app/ui/brush_popup.h"
#include "app/ui/button_set.h"
#include "app/ui/color_button.h"
#include "app/ui/skin/skin_theme.h"
@ -33,7 +37,6 @@
#include "doc/conversion_she.h"
#include "doc/image.h"
#include "doc/palette.h"
#include "she/scoped_surface_lock.h"
#include "she/surface.h"
#include "she/system.h"
#include "ui/button.h"
@ -55,17 +58,16 @@ using namespace tools;
static bool g_updatingFromTool = false;
class ContextBar::BrushTypeField : public ButtonSet {
class ContextBar::BrushTypeField : public ButtonSet
, public BrushPopupDelegate {
public:
BrushTypeField()
BrushTypeField(ContextBar* owner)
: ButtonSet(1)
, m_popupWindow(NULL)
, m_brushTypeButton(NULL) {
m_bitmap = she::instance()->createRgbaSurface(8, 8);
she::ScopedSurfaceLock lock(m_bitmap);
lock->clear();
, m_owner(owner)
, m_bitmap(BrushPopup::createSurfaceForBrush(BrushRef(nullptr)))
, m_popupWindow(this) {
addItem(m_bitmap);
m_popupWindow.BrushChange.connect(&BrushTypeField::onBrushChange, this);
}
~BrushTypeField() {
@ -74,34 +76,25 @@ public:
m_bitmap->dispose();
}
void setBrushSettings(IBrushSettings* brushSettings) {
base::UniquePtr<Palette> palette(new Palette(frame_t(0), 2));
palette->setEntry(0, doc::rgba(0, 0, 0, 0));
palette->setEntry(1, doc::rgba(0, 0, 0, 255));
base::UniquePtr<Brush> brush(
new Brush(
m_brushType = brushSettings->getType(),
std::min(10, brushSettings->getSize()),
brushSettings->getAngle()));
Image* image = brush->image();
void updateBrush(tools::Tool* tool = nullptr) {
if (m_bitmap)
m_bitmap->dispose();
m_bitmap = she::instance()->createRgbaSurface(image->width(), image->height());
convert_image_to_surface(image, palette, m_bitmap,
0, 0, 0, 0, image->width(), image->height());
m_bitmap = BrushPopup::createSurfaceForBrush(
m_owner->activeBrush(tool));
getItem(0)->setIcon(m_bitmap);
}
void setupTooltips(TooltipManager* tooltipManager) {
m_popupWindow.setupTooltips(tooltipManager);
}
protected:
void onItemChange() override {
ButtonSet::onItemChange();
if (!m_popupWindow || !m_popupWindow->isVisible())
if (!m_popupWindow.isVisible())
openPopup();
else
closePopup();
@ -111,59 +104,61 @@ protected:
ev.setPreferredSize(Size(16, 18)*guiscale());
}
private:
void openPopup() {
SkinTheme* theme = static_cast<SkinTheme*>(getTheme());
// BrushPopupDelegate impl
void onDeleteBrushSlot(int slot) override {
m_owner->removeBrush(slot);
}
void onDeleteAllBrushes() override {
m_owner->removeAllBrushes();
}
private:
// Returns a little rectangle that can be used by the popup as the
// first brush position.
gfx::Rect getPopupBox() {
Rect rc = getBounds();
rc.y += rc.h - 2*guiscale();
rc.setSize(getPreferredSize());
rc.w *= 3;
m_popupWindow = new PopupWindow("", PopupWindow::kCloseOnClickInOtherWindow);
m_popupWindow->setAutoRemap(false);
m_popupWindow->setBorder(Border(0));
m_popupWindow->setBounds(rc);
m_popupWindow->child_spacing = 0;
Region rgn(m_popupWindow->getBounds().createUnion(getBounds()));
m_popupWindow->setHotRegion(rgn);
m_brushTypeButton = new ButtonSet(3);
m_brushTypeButton->addItem(theme->get_part(PART_BRUSH_CIRCLE));
m_brushTypeButton->addItem(theme->get_part(PART_BRUSH_SQUARE));
m_brushTypeButton->addItem(theme->get_part(PART_BRUSH_LINE));
m_brushTypeButton->setSelectedItem(m_brushType);
m_brushTypeButton->ItemChange.connect(&BrushTypeField::onBrushTypeChange, this);
m_brushTypeButton->setTransparent(true);
m_brushTypeButton->setBgColor(gfx::ColorNone);
m_popupWindow->addChild(m_brushTypeButton);
m_popupWindow->openWindow();
return rc;
}
void closePopup() {
if (m_popupWindow) {
m_popupWindow->closeWindow(NULL);
delete m_popupWindow;
m_popupWindow = NULL;
m_brushTypeButton = NULL;
}
}
void onBrushTypeChange() {
m_brushType = (BrushType)m_brushTypeButton->selectedItem();
void openPopup() {
ISettings* settings = UIContext::instance()->settings();
Tool* currentTool = settings->getCurrentTool();
IBrushSettings* brushSettings = settings->getToolSettings(currentTool)->getBrush();
brushSettings->setType(m_brushType);
doc::BrushRef brush = m_owner->activeBrush();
setBrushSettings(brushSettings);
m_popupWindow.regenerate(getPopupBox(), m_owner->getBrushes());
m_popupWindow.setBrush(brush.get());
Region rgn(m_popupWindow.getBounds().createUnion(getBounds()));
m_popupWindow.setHotRegion(rgn);
m_popupWindow.openWindow();
}
void closePopup() {
m_popupWindow.closeWindow(NULL);
}
void onBrushChange(const BrushRef& brush) {
if (brush->type() == kImageBrushType)
m_owner->setActiveBrush(brush);
else {
ISettings* settings = UIContext::instance()->settings();
Tool* currentTool = settings->getCurrentTool();
IBrushSettings* brushSettings = settings->getToolSettings(currentTool)->getBrush();
brushSettings->setType(brush->type());
m_owner->setActiveBrush(
ContextBar::createBrushFromSettings(brushSettings));
}
}
ContextBar* m_owner;
she::Surface* m_bitmap;
BrushType m_brushType;
PopupWindow* m_popupWindow;
ButtonSet* m_brushTypeButton;
BrushPopup m_popupWindow;
};
class ContextBar::BrushSizeField : public IntEntry
@ -208,15 +203,59 @@ protected:
->getBrush()
->setAngle(getValue());
IToolSettings* toolSettings = settings->getToolSettings(currentTool);
IBrushSettings* brushSettings = toolSettings->getBrush();
m_brushType->setBrushSettings(brushSettings);
m_brushType->updateBrush();
}
private:
BrushTypeField* m_brushType;
};
class ContextBar::BrushPatternField : public ComboBox
{
public:
BrushPatternField() : m_lock(false) {
addItem("Pattern aligned to source");
addItem("Pattern aligned to destination");
addItem("Paint brush");
}
void setBrushPattern(BrushPattern type) {
int index = 0;
switch (type) {
case BrushPattern::ALIGNED_TO_SRC: index = 0; break;
case BrushPattern::ALIGNED_TO_DST: index = 1; break;
case BrushPattern::PAINT_BRUSH: index = 2; break;
}
m_lock = true;
setSelectedItemIndex(index);
m_lock = false;
}
protected:
void onChange() override {
ComboBox::onChange();
if (m_lock)
return;
BrushPattern type = BrushPattern::ALIGNED_TO_SRC;
switch (getSelectedItemIndex()) {
case 0: type = BrushPattern::ALIGNED_TO_SRC; break;
case 1: type = BrushPattern::ALIGNED_TO_DST; break;
case 2: type = BrushPattern::PAINT_BRUSH; break;
}
ISettings* settings = UIContext::instance()->settings();
Tool* currentTool = settings->getCurrentTool();
App::instance()->preferences().brush.pattern(type);
}
bool m_lock;
};
class ContextBar::ToleranceField : public IntEntry
{
public:
@ -742,9 +781,10 @@ ContextBar::ContextBar()
m_selectionOptionsBox->addChild(m_transparentColor = new TransparentColorField);
m_selectionOptionsBox->addChild(m_rotAlgo = new RotAlgorithmField());
addChild(m_brushType = new BrushTypeField());
addChild(m_brushType = new BrushTypeField(this));
addChild(m_brushSize = new BrushSizeField());
addChild(m_brushAngle = new BrushAngleField(m_brushType));
addChild(m_brushPatternField = new BrushPatternField());
addChild(m_toleranceLabel = new Label("Tolerance:"));
addChild(m_tolerance = new ToleranceField());
@ -796,6 +836,8 @@ ContextBar::ContextBar()
"component is used to setup the opacity level of all drawing tools.\n\n"
"When unchecked -the default behavior- the color is picked\n"
"from the composition of all sprite layers.", JI_LEFT | JI_TOP);
m_brushType->setupTooltips(tooltipManager);
m_selectionMode->setupTooltips(tooltipManager);
m_dropPixels->setupTooltips(tooltipManager);
m_freehandAlgo->setupTooltips(tooltipManager);
@ -805,7 +847,7 @@ ContextBar::ContextBar()
App::instance()->CurrentToolChange.connect(&ContextBar::onCurrentToolChange, this);
m_dropPixels->DropPixels.connect(&ContextBar::onDropPixels, this);
onCurrentToolChange();
setActiveBrush(createBrushFromSettings());
}
ContextBar::~ContextBar()
@ -831,30 +873,24 @@ void ContextBar::onSetOpacity(int newOpacity)
void ContextBar::onBrushSizeChange()
{
ISettings* settings = UIContext::instance()->settings();
Tool* currentTool = settings->getCurrentTool();
IToolSettings* toolSettings = settings->getToolSettings(currentTool);
IBrushSettings* brushSettings = toolSettings->getBrush();
m_brushType->setBrushSettings(brushSettings);
m_brushSize->setTextf("%d", brushSettings->getSize());
if (m_activeBrush->type() != kImageBrushType)
discardActiveBrush();
}
void ContextBar::onBrushAngleChange()
{
ISettings* settings = UIContext::instance()->settings();
Tool* currentTool = settings->getCurrentTool();
IToolSettings* toolSettings = settings->getToolSettings(currentTool);
IBrushSettings* brushSettings = toolSettings->getBrush();
m_brushType->setBrushSettings(brushSettings);
m_brushAngle->setTextf("%d", brushSettings->getAngle());
if (m_activeBrush->type() != kImageBrushType)
discardActiveBrush();
}
void ContextBar::onCurrentToolChange()
{
ISettings* settings = UIContext::instance()->settings();
updateFromTool(settings->getCurrentTool());
if (m_activeBrush->type() != kImageBrushType)
setActiveBrush(ContextBar::createBrushFromSettings());
else {
ISettings* settings = UIContext::instance()->settings();
updateFromTool(settings->getCurrentTool());
}
}
void ContextBar::onDropPixels(ContextBarObserver::DropAction action)
@ -875,9 +911,11 @@ void ContextBar::updateFromTool(tools::Tool* tool)
m_toolSettings = toolSettings;
m_toolSettings->addObserver(this);
m_brushType->setBrushSettings(brushSettings);
m_brushType->updateBrush(tool);
m_brushSize->setTextf("%d", brushSettings->getSize());
m_brushAngle->setTextf("%d", brushSettings->getAngle());
m_brushPatternField->setBrushPattern(
App::instance()->preferences().brush.pattern());
m_tolerance->setTextf("%d", toolSettings->getTolerance());
m_contiguous->setSelected(toolSettings->getContiguous());
@ -898,6 +936,9 @@ void ContextBar::updateFromTool(tools::Tool* tool)
tool->getInk(1)->isPaint() ||
tool->getInk(1)->isEffect());
// True if we have an image as brush
bool hasImageBrush = (activeBrush()->type() == kImageBrushType);
// True if the current tool is eyedropper.
bool isEyedropper =
(tool->getInk(0)->isEyedropper() ||
@ -929,10 +970,11 @@ void ContextBar::updateFromTool(tools::Tool* tool)
// Show/Hide fields
m_brushType->setVisible(hasOpacity);
m_brushSize->setVisible(hasOpacity);
m_brushAngle->setVisible(hasOpacity);
m_brushSize->setVisible(hasOpacity && !hasImageBrush);
m_brushAngle->setVisible(hasOpacity && !hasImageBrush);
m_brushPatternField->setVisible(hasOpacity && hasImageBrush);
m_opacityLabel->setVisible(hasOpacity);
m_inkType->setVisible(hasInk);
m_inkType->setVisible(hasInk && !hasImageBrush);
m_inkOpacity->setVisible(hasOpacity);
m_grabAlpha->setVisible(isEyedropper);
m_autoSelectLayer->setVisible(isMove);
@ -977,4 +1019,102 @@ void ContextBar::updateAutoSelectLayer(bool state)
m_autoSelectLayer->setSelected(state);
}
int ContextBar::addBrush(const doc::BrushRef& brush)
{
// Use an empty slot
for (size_t i=0; i<m_brushes.size(); ++i) {
if (!m_brushes[i].locked ||
!m_brushes[i].brush) {
m_brushes[i].brush = brush;
return i+1;
}
}
m_brushes.push_back(BrushSlot(brush));
return (int)m_brushes.size(); // Returns the slot
}
void ContextBar::removeBrush(int slot)
{
--slot;
if (slot >= 0 && slot < (int)m_brushes.size()) {
m_brushes[slot].brush.reset();
// Erase empty trailing slots
while (!m_brushes.empty() &&
!m_brushes[m_brushes.size()-1].brush)
m_brushes.erase(--m_brushes.end());
}
}
void ContextBar::removeAllBrushes()
{
while (!m_brushes.empty())
m_brushes.erase(--m_brushes.end());
}
void ContextBar::setActiveBrushBySlot(int slot)
{
--slot;
if (slot >= 0 && slot < (int)m_brushes.size() &&
m_brushes[slot].brush) {
m_brushes[slot].locked = true;
setActiveBrush(m_brushes[slot].brush);
}
}
Brushes ContextBar::getBrushes()
{
Brushes brushes;
for (const auto& slot : m_brushes)
brushes.push_back(slot.brush);
return brushes;
}
void ContextBar::setActiveBrush(const doc::BrushRef& brush)
{
m_activeBrush = brush;
ISettings* settings = UIContext::instance()->settings();
updateFromTool(settings->getCurrentTool());
}
doc::BrushRef ContextBar::activeBrush(tools::Tool* tool) const
{
if (!tool ||
(tool->getInk(0)->isPaint() &&
m_activeBrush->type() == kImageBrushType)) {
m_activeBrush->setPattern(App::instance()->preferences().brush.pattern());
return m_activeBrush;
}
ISettings* settings = UIContext::instance()->settings();
IToolSettings* toolSettings = settings->getToolSettings(tool);
return ContextBar::createBrushFromSettings(toolSettings->getBrush());
}
void ContextBar::discardActiveBrush()
{
setActiveBrush(ContextBar::createBrushFromSettings());
}
// static
doc::BrushRef ContextBar::createBrushFromSettings(IBrushSettings* brushSettings)
{
if (brushSettings == nullptr) {
ISettings* settings = UIContext::instance()->settings();
tools::Tool* tool = settings->getCurrentTool();
IToolSettings* toolSettings = settings->getToolSettings(tool);
brushSettings = toolSettings->getBrush();
}
doc::BrushRef brush;
brush.reset(
new Brush(
brushSettings->getType(),
brushSettings->getSize(),
brushSettings->getAngle()));
return brush;
}
} // namespace app

View File

@ -12,10 +12,15 @@
#include "app/settings/settings_observers.h"
#include "app/ui/context_bar_observer.h"
#include "base/observable.h"
#include "doc/brush.h"
#include "doc/brushes.h"
#include "ui/box.h"
#include <vector>
namespace ui {
class Box;
class Button;
class Label;
}
@ -25,6 +30,7 @@ namespace tools {
namespace app {
class IBrushSettings;
class IToolSettings;
class ContextBar : public ui::Box,
@ -39,6 +45,21 @@ namespace app {
void updateSelectionMode(SelectionMode mode);
void updateAutoSelectLayer(bool state);
void setActiveBrush(const doc::BrushRef& brush);
doc::BrushRef activeBrush(tools::Tool* tool = nullptr) const;
void discardActiveBrush();
// Adds a new brush and returns the slot number where the brush
// is now available.
int addBrush(const doc::BrushRef& brush);
void removeBrush(int slot);
void removeAllBrushes();
void setActiveBrushBySlot(int slot);
doc::Brushes getBrushes();
static doc::BrushRef createBrushFromSettings(
IBrushSettings* brushSettings = nullptr);
protected:
bool onProcessMessage(ui::Message* msg) override;
void onPreferredSize(ui::PreferredSizeEvent& ev) override;
@ -52,6 +73,21 @@ namespace app {
void onCurrentToolChange();
void onDropPixels(ContextBarObserver::DropAction action);
struct BrushSlot {
// True if the user locked the brush using the shortcut key to
// access it.
bool locked;
// Can be null if the user deletes the brush.
doc::BrushRef brush;
BrushSlot(const doc::BrushRef& brush)
: locked(false), brush(brush) {
}
};
typedef std::vector<BrushSlot> BrushSlots;
class BrushTypeField;
class BrushAngleField;
class BrushSizeField;
@ -65,6 +101,7 @@ namespace app {
class TransparentColorField;
class RotAlgorithmField;
class FreehandAlgorithmField;
class BrushPatternField;
class GrabAlphaField;
class DropPixelsField;
class AutoSelectLayerField;
@ -83,6 +120,7 @@ namespace app {
AutoSelectLayerField* m_autoSelectLayer;
ui::Box* m_freehandBox;
FreehandAlgorithmField* m_freehandAlgo;
BrushPatternField* m_brushPatternField;
ui::Box* m_sprayBox;
SprayWidthField* m_sprayWidth;
SpraySpeedField* m_spraySpeed;
@ -91,6 +129,8 @@ namespace app {
TransparentColorField* m_transparentColor;
RotAlgorithmField* m_rotAlgo;
DropPixelsField* m_dropPixels;
doc::BrushRef m_activeBrush;
BrushSlots m_brushes;
};
} // namespace app

View File

@ -74,6 +74,10 @@ public:
set_current_palette(editor->sprite()->palette(editor->frame()), true);
}
void onAfterLayerChanged(Editor* editor) override {
App::instance()->getMainWindow()->getPreviewEditor()->updateUsingEditor(this);
}
// EditorCustomizationDelegate implementation
tools::Tool* getQuickTool(tools::Tool* currentTool) override {
return KeyboardShortcuts::instance()

View File

@ -17,17 +17,27 @@
#include "app/ini_file.h"
#include "app/modules/editors.h"
#include "app/settings/settings.h"
#include "app/tools/controller.h"
#include "app/tools/ink.h"
#include "app/tools/intertwine.h"
#include "app/tools/point_shape.h"
#include "app/tools/tool.h"
#include "app/tools/tool_loop.h"
#include "app/ui/context_bar.h"
#include "app/ui/editor/tool_loop_impl.h"
#include "app/ui/main_window.h"
#include "app/ui_context.h"
#include "app/util/boundary.h"
#include "base/memory.h"
#include "doc/algo.h"
#include "doc/brush.h"
#include "doc/cel.h"
#include "doc/image.h"
#include "doc/layer.h"
#include "doc/primitives.h"
#include "doc/site.h"
#include "doc/sprite.h"
#include "render/render.h"
#include "ui/base.h"
#include "ui/system.h"
#include "ui/widget.h"
@ -110,8 +120,8 @@ void Editor::set_cursor_color(const app::Color& color)
// Slots for App signals
//////////////////////////////////////////////////////////////////////
static gfx::Rect lastBrushBounds;
static int brush_size_thick = 0;
static Brush* current_brush = NULL;
static void on_palette_change_update_cursor_color()
{
@ -136,29 +146,9 @@ static void on_brush_after_change()
}
}
static Brush* editor_get_current_brush(Editor* editor)
static Brush* get_current_brush()
{
// Create the current brush from settings
tools::Tool* tool = editor->getCurrentEditorTool();
IBrushSettings* brush_settings = UIContext::instance()
->settings()
->getToolSettings(tool)
->getBrush();
ASSERT(brush_settings != NULL);
if (!current_brush ||
current_brush->type() != brush_settings->getType() ||
current_brush->size() != brush_settings->getSize() ||
current_brush->angle() != brush_settings->getAngle()) {
delete current_brush;
current_brush = new Brush(
brush_settings->getType(),
brush_settings->getSize(),
brush_settings->getAngle());
}
return current_brush;
return App::instance()->getMainWindow()->getContextBar()->activeBrush().get();
}
//////////////////////////////////////////////////////////////////////
@ -183,9 +173,6 @@ void Editor::editor_cursor_exit()
if (cursor_bound.seg != NULL)
base_free(cursor_bound.seg);
delete current_brush;
current_brush = NULL;
}
// Draws the brush cursor inside the specified editor.
@ -244,13 +231,20 @@ void Editor::drawBrushPreview(const gfx::Point& pos, bool refresh)
->settings()
->getToolSettings(tool);
Brush* brush = editor_get_current_brush(this);
Brush* brush = get_current_brush();
gfx::Rect brushBounds = brush->bounds();
brushBounds.offset(spritePos);
// Create the extra cel to show the brush preview
Site site = getSite();
Cel* cel = site.cel();
m_document->prepareExtraCel(
gfx::Rect(brushBounds).offset(spritePos),
tool_settings->getOpacity());
brushBounds,
cel ? cel->opacity(): 255);
m_document->setExtraCelType(render::ExtraType::NONE);
m_document->setExtraCelBlendMode(
m_layer ? static_cast<LayerImage*>(m_layer)->getBlendMode(): BLEND_MODE_NORMAL);
// In 'indexed' images, if the current color is 0, we have to use
// a different mask color (different from 0) to draw the extra layer
@ -259,16 +253,36 @@ void Editor::drawBrushPreview(const gfx::Point& pos, bool refresh)
Image* extraImage = m_document->getExtraCelImage();
extraImage->setMaskColor(mask_color);
draw_brush(extraImage, brush, -brushBounds.x, -brushBounds.y,
brush_color, extraImage->maskColor());
clear_image(extraImage, mask_color);
if (m_layer) {
render::Render().renderLayer(
extraImage, m_layer, m_frame,
gfx::Clip(0, 0, brushBounds),
BLEND_MODE_COPY);
// This extra cel is a patch for the current layer/frame
m_document->setExtraCelType(render::ExtraType::PATCH);
}
tools::ToolLoop* loop = create_tool_loop_preview(
this, UIContext::instance(), extraImage,
-gfx::Point(brushBounds.x,
brushBounds.y));
if (loop) {
loop->getInk()->prepareInk(loop);
loop->getIntertwine()->prepareIntertwine();
loop->getController()->prepareController();
loop->getPointShape()->preparePointShape(loop);
loop->getPointShape()->transformPoint(
loop, -brush->bounds().x, -brush->bounds().y);
delete loop;
}
if (refresh) {
m_document->notifySpritePixelsModified
(m_sprite,
gfx::Region(gfx::Rect(
spritePos.x+brushBounds.x,
spritePos.y+brushBounds.y,
brushBounds.w, brushBounds.h)));
(m_sprite, gfx::Region(lastBrushBounds = brushBounds));
}
}
@ -313,12 +327,14 @@ void Editor::moveBrushPreview(const gfx::Point& pos, bool refresh)
}
if (cursor_type & CURSOR_THINCROSS && m_state->requireBrushPreview()) {
Brush* brush = editor_get_current_brush(this);
gfx::Rect brushBounds = brush->bounds();
gfx::Rect rc1(oldEditorPos.x+brushBounds.x, oldEditorPos.y+brushBounds.y, brushBounds.w, brushBounds.h);
gfx::Rect rc2(newEditorPos.x+brushBounds.x, newEditorPos.y+brushBounds.y, brushBounds.w, brushBounds.h);
Brush* brush = get_current_brush();
gfx::Rect newBrushBounds = brush->bounds();
newBrushBounds.offset(newEditorPos);
m_document->notifySpritePixelsModified
(m_sprite, gfx::Region(rc1.createUnion(rc2)));
(m_sprite, gfx::Region(lastBrushBounds.createUnion(newBrushBounds)));
lastBrushBounds = newBrushBounds;
}
// Save area and draw the cursor
@ -360,20 +376,10 @@ void Editor::clearBrushPreview(bool refresh)
// Clean pixel/brush preview
if (cursor_type & CURSOR_THINCROSS && m_state->requireBrushPreview()) {
Brush* brush = editor_get_current_brush(this);
gfx::Rect brushBounds = brush->bounds();
m_document->prepareExtraCel(
gfx::Rect(brushBounds).offset(pos),
0); // Opacity = 0
m_document->destroyExtraCel();
if (refresh) {
m_document->notifySpritePixelsModified
(m_sprite,
gfx::Region(gfx::Rect(
pos.x+brushBounds.x,
pos.y+brushBounds.y,
brushBounds.w, brushBounds.h)));
(m_sprite, gfx::Region(lastBrushBounds));
}
}

View File

@ -100,17 +100,14 @@ class EditorPostRenderImpl : public EditorPostRender {
public:
EditorPostRenderImpl(Editor* editor, Graphics* g)
: m_editor(editor)
, m_g(g)
{
, m_g(g) {
}
Editor* getEditor()
{
Editor* getEditor() {
return m_editor;
}
void drawLine(int x1, int y1, int x2, int y2, gfx::Color screenColor)
{
void drawLine(int x1, int y1, int x2, int y2, gfx::Color screenColor) override {
gfx::Point a(x1, y1);
gfx::Point b(x2, y2);
a = m_editor->editorToScreen(a);
@ -123,6 +120,17 @@ public:
m_g->drawLine(screenColor, a, b);
}
void drawRectXor(const gfx::Rect& rc) override {
gfx::Rect rc2 = m_editor->editorToScreen(rc);
gfx::Rect bounds = m_editor->getBounds();
rc2.x -= bounds.x;
rc2.y -= bounds.y;
m_g->setDrawMode(Graphics::DrawMode::Xor);
m_g->drawRect(gfx::rgba(255, 255, 255), rc2);
m_g->setDrawMode(Graphics::DrawMode::Solid);
}
private:
Editor* m_editor;
Graphics* m_g;
@ -421,11 +429,16 @@ void Editor::drawOneSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& sprite
}
}
m_renderEngine.setExtraImage(
m_document->getExtraCel(),
m_document->getExtraCelImage(),
m_document->getExtraCelBlendMode(),
m_layer, m_frame);
if (m_document->getExtraCelType() != render::ExtraType::NONE) {
ASSERT(m_document->getExtraCel());
m_renderEngine.setExtraImage(
m_document->getExtraCelType(),
m_document->getExtraCel(),
m_document->getExtraCelImage(),
m_document->getExtraCelBlendMode(),
m_layer, m_frame);
}
m_renderEngine.renderSprite(rendered, m_sprite, m_frame,
gfx::Clip(0, 0, rc), m_zoom);
@ -758,6 +771,8 @@ void Editor::flashCurrentLayer()
m_renderEngine.removePreviewImage();
m_document->prepareExtraCel(m_sprite->bounds(), 255);
m_document->setExtraCelType(render::ExtraType::COMPOSITE);
Image* flash_image = m_document->getExtraCelImage();
clear_image(flash_image, flash_image->maskColor());
@ -858,9 +873,13 @@ tools::Tool* Editor::getCurrentEditorTool()
tools::Ink* Editor::getCurrentEditorInk()
{
tools::Ink* ink = m_state->getStateInk();
if (ink)
return ink;
Context* context = UIContext::instance();
tools::Tool* tool = getCurrentEditorTool();
tools::Ink* ink = tool->getInk(m_secondaryButton ? 1: 0);
ink = tool->getInk(m_secondaryButton ? 1: 0);
if (m_quicktool)
return ink;

View File

@ -38,6 +38,7 @@ namespace app {
virtual ~EditorPostRender() { }
virtual Editor* getEditor() = 0;
virtual void drawLine(int x1, int y1, int x2, int y2, gfx::Color screenColor) = 0;
virtual void drawRectXor(const gfx::Rect& rc) = 0;
};
// Used by editor's states to pre- and post-render customized

View File

@ -26,6 +26,7 @@ namespace app {
class EditorDecorator;
namespace tools {
class Ink;
class Tool;
}
@ -107,6 +108,9 @@ namespace app {
// Returns true if this state accept the given quicktool.
virtual bool acceptQuickTool(tools::Tool* tool) { return true; }
// Custom ink in this state.
virtual tools::Ink* getStateInk() { return nullptr; }
private:
DISABLE_COPYING(EditorState);
};

View File

@ -31,6 +31,7 @@
#include "doc/algorithm/rotsprite.h"
#include "doc/cel.h"
#include "doc/image.h"
#include "doc/layer.h"
#include "doc/mask.h"
#include "doc/site.h"
#include "doc/sprite.h"
@ -66,6 +67,9 @@ PixelsMovement::PixelsMovement(Context* context,
ContextWriter writer(m_reader, 500);
m_document->prepareExtraCel(m_sprite->bounds(), opacity);
m_document->setExtraCelType(render::ExtraType::COMPOSITE);
m_document->setExtraCelBlendMode(
static_cast<LayerImage*>(m_layer)->getBlendMode());
redrawExtraImage();

View File

@ -11,10 +11,12 @@
#include "app/ui/editor/select_box_state.h"
#include "app/app.h"
#include "app/tools/tool_box.h"
#include "app/ui/editor/editor.h"
#include "gfx/rect.h"
#include "doc/image.h"
#include "doc/sprite.h"
#include "gfx/rect.h"
#include "ui/message.h"
#include "ui/system.h"
#include "ui/view.h"
@ -23,11 +25,12 @@ namespace app {
using namespace ui;
SelectBoxState::SelectBoxState(SelectBoxDelegate* delegate, const gfx::Rect& rc, PaintFlags paintFlags)
SelectBoxState::SelectBoxState(SelectBoxDelegate* delegate, const gfx::Rect& rc, Flags flags)
: m_delegate(delegate)
, m_rulers(4)
, m_movingRuler(-1)
, m_paintFlags(paintFlags)
, m_selectingBox(false)
, m_flags(flags)
{
setBoxBounds(rc);
}
@ -66,29 +69,51 @@ bool SelectBoxState::onMouseDown(Editor* editor, MouseMessage* msg)
if (msg->left() || msg->right()) {
m_movingRuler = -1;
for (int i=0; i<(int)m_rulers.size(); ++i) {
if (touchRuler(editor, m_rulers[i], msg->position().x, msg->position().y)) {
m_movingRuler = i;
break;
if (hasFlag(RULERS)) {
for (int i=0; i<(int)m_rulers.size(); ++i) {
if (touchRuler(editor, m_rulers[i], msg->position().x, msg->position().y)) {
m_movingRuler = i;
break;
}
}
}
if (hasFlag(QUICKBOX) && m_movingRuler == -1) {
m_selectingBox = true;
m_selectingButtons = msg->buttons();
m_startingPos = editor->screenToEditor(msg->position());
setBoxBounds(gfx::Rect(m_startingPos, gfx::Size(1, 1)));
}
editor->captureMouse();
return true;
}
return StandbyState::onMouseDown(editor, msg);
}
bool SelectBoxState::onMouseUp(Editor* editor, MouseMessage* msg)
{
m_movingRuler = -1;
if (m_selectingBox) {
m_selectingBox = false;
if (m_delegate) {
if (m_selectingButtons == msg->buttons())
m_delegate->onQuickboxEnd(getBoxBounds(), msg->buttons());
else
m_delegate->onQuickboxCancel();
}
}
return StandbyState::onMouseUp(editor, msg);
}
bool SelectBoxState::onMouseMove(Editor* editor, MouseMessage* msg)
{
if (m_movingRuler >= 0) {
bool used = false;
if (hasFlag(RULERS) && m_movingRuler >= 0) {
gfx::Point pt = editor->screenToEditor(msg->position());
switch (m_rulers[m_movingRuler].getOrientation()) {
@ -101,48 +126,85 @@ bool SelectBoxState::onMouseMove(Editor* editor, MouseMessage* msg)
m_rulers[m_movingRuler].setPosition(pt.x);
break;
}
used = true;
}
if (hasFlag(QUICKBOX) && m_selectingBox) {
gfx::Point p1 = m_startingPos;
gfx::Point p2 = editor->screenToEditor(msg->position());
if (p2.x < p1.x) std::swap(p1.x, p2.x);
if (p2.y < p1.y) std::swap(p1.y, p2.y);
++p2.x;
++p2.y;
setBoxBounds(gfx::Rect(p1, p2));
used = true;
}
if (used) {
if (m_delegate)
m_delegate->onChangeRectangle(getBoxBounds());
editor->invalidate();
return true;
}
return StandbyState::onMouseMove(editor, msg);
else
return StandbyState::onMouseMove(editor, msg);
}
bool SelectBoxState::onSetCursor(Editor* editor)
{
if (m_movingRuler >= 0) {
switch (m_rulers[m_movingRuler].getOrientation()) {
case Ruler::Horizontal: ui::set_mouse_cursor(kSizeNSCursor); return true;
case Ruler::Vertical: ui::set_mouse_cursor(kSizeWECursor); return true;
if (hasFlag(RULERS)) {
if (m_movingRuler >= 0) {
switch (m_rulers[m_movingRuler].getOrientation()) {
case Ruler::Horizontal: ui::set_mouse_cursor(kSizeNSCursor); return true;
case Ruler::Vertical: ui::set_mouse_cursor(kSizeWECursor); return true;
}
}
}
int x = ui::get_mouse_position().x;
int y = ui::get_mouse_position().y;
int x = ui::get_mouse_position().x;
int y = ui::get_mouse_position().y;
for (Rulers::iterator it = m_rulers.begin(), end = m_rulers.end(); it != end; ++it) {
if (touchRuler(editor, *it, x, y)) {
switch (it->getOrientation()) {
case Ruler::Horizontal:
ui::set_mouse_cursor(kSizeNSCursor);
return true;
case Ruler::Vertical:
ui::set_mouse_cursor(kSizeWECursor);
return true;
for (Rulers::iterator it = m_rulers.begin(), end = m_rulers.end(); it != end; ++it) {
if (touchRuler(editor, *it, x, y)) {
switch (it->getOrientation()) {
case Ruler::Horizontal:
ui::set_mouse_cursor(kSizeNSCursor);
return true;
case Ruler::Vertical:
ui::set_mouse_cursor(kSizeWECursor);
return true;
}
}
}
}
return StandbyState::onSetCursor(editor);
}
bool SelectBoxState::requireBrushPreview()
{
if (hasFlag(QUICKBOX))
return true;
// Returns false as it overrides default standby state behavior &
// look. This state uses normal arrow cursors.
return false;
}
tools::Ink* SelectBoxState::getStateInk()
{
if (hasFlag(QUICKBOX))
return App::instance()->getToolBox()->getInkById(
tools::WellKnownInks::Selection);
else
return nullptr;
}
void SelectBoxState::preRenderDecorator(EditorPreRender* render)
{
// Without black shadow?
if (!hasPaintFlag(PaintDarkOutside))
if (!hasFlag(DARKOUTSIDE))
return;
gfx::Rect rc = getBoxBounds();
@ -177,7 +239,7 @@ void SelectBoxState::postRenderDecorator(EditorPostRender* render)
vp = editor->screenToEditor(vp);
// Paint a grid generated by the box
if (hasPaintFlag(PaintGrid)) {
if (hasFlag(GRID)) {
gfx::Color gridColor = gfx::rgba(100, 200, 100);
gfx::Rect boxBounds = getBoxBounds();
@ -191,7 +253,7 @@ void SelectBoxState::postRenderDecorator(EditorPostRender* render)
}
// Draw the rulers enclosing the box
if (hasPaintFlag(PaintRulers)) {
if (hasFlag(RULERS)) {
gfx::Color rulerColor = gfx::rgba(0, 0, 255);
for (Rulers::iterator it = m_rulers.begin(), end = m_rulers.end(); it != end; ++it) {
@ -207,6 +269,10 @@ void SelectBoxState::postRenderDecorator(EditorPostRender* render)
}
}
}
if (hasFlag(QUICKBOX)) {
render->drawRectXor(getBoxBounds());
}
}
bool SelectBoxState::touchRuler(Editor* editor, Ruler& ruler, int x, int y)
@ -222,9 +288,9 @@ bool SelectBoxState::touchRuler(Editor* editor, Ruler& ruler, int x, int y)
return false;
}
bool SelectBoxState::hasPaintFlag(PaintFlags flag) const
bool SelectBoxState::hasFlag(Flags flag) const
{
return ((m_paintFlags & flag) == flag);
return ((m_flags & flag) == flag);
}
} // namespace app

View File

@ -12,6 +12,7 @@
#include "app/ui/editor/editor_decorator.h"
#include "app/ui/editor/ruler.h"
#include "app/ui/editor/standby_state.h"
#include "ui/mouse_buttons.h"
#include <vector>
@ -20,7 +21,15 @@ namespace app {
class SelectBoxDelegate {
public:
virtual ~SelectBoxDelegate() { }
virtual void onChangeRectangle(const gfx::Rect& rect) = 0;
// Called each time the selected box is modified (e.g. rulers are
// moved).
virtual void onChangeRectangle(const gfx::Rect& rect) { }
// Called only in QUICKBOX mode, when the user released the mouse
// button.
virtual void onQuickboxEnd(const gfx::Rect& rect, ui::MouseButtons buttons) { }
virtual void onQuickboxCancel() { }
};
class SelectBoxState : public StandbyState
@ -28,14 +37,15 @@ namespace app {
enum { H1, H2, V1, V2 };
public:
typedef int PaintFlags;
static const int PaintRulers = 1;
static const int PaintDarkOutside = 2;
static const int PaintGrid = 4;
typedef int Flags;
static const int RULERS = 1; // Draw rulers at each edge of the current box
static const int DARKOUTSIDE = 2; // The outside of the box must be darker
static const int GRID = 4; // Draw a grid
static const int QUICKBOX = 8; // Select the box as in selection tool, drawing a boxu
SelectBoxState(SelectBoxDelegate* delegate,
const gfx::Rect& rc,
PaintFlags paintFlags);
Flags flags);
// Returns the bounding box arranged by the rulers.
gfx::Rect getBoxBounds() const;
@ -48,10 +58,8 @@ namespace app {
virtual bool onMouseUp(Editor* editor, ui::MouseMessage* msg) override;
virtual bool onMouseMove(Editor* editor, ui::MouseMessage* msg) override;
virtual bool onSetCursor(Editor* editor) override;
// Returns false as it overrides default standby state behavior &
// look. This state uses normal arrow cursors.
virtual bool requireBrushPreview() override { return false; }
virtual bool requireBrushPreview() override;
virtual tools::Ink* getStateInk() override;
// EditorDecorator overrides
virtual void preRenderDecorator(EditorPreRender* render) override;
@ -64,12 +72,15 @@ namespace app {
// the given ruler.
bool touchRuler(Editor* editor, Ruler& ruler, int x, int y);
bool hasPaintFlag(PaintFlags flag) const;
bool hasFlag(Flags flag) const;
SelectBoxDelegate* m_delegate;
Rulers m_rulers;
int m_movingRuler;
PaintFlags m_paintFlags;
bool m_selectingBox;
ui::MouseButtons m_selectingButtons;
gfx::Point m_startingPos;
Flags m_flags;
};
} // namespace app

View File

@ -24,6 +24,7 @@
#include "app/settings/settings.h"
#include "app/tools/controller.h"
#include "app/tools/ink.h"
#include "app/tools/point_shape.h"
#include "app/tools/shade_table.h"
#include "app/tools/shading_options.h"
#include "app/tools/tool.h"
@ -31,11 +32,14 @@
#include "app/tools/tool_loop.h"
#include "app/transaction.h"
#include "app/ui/color_bar.h"
#include "app/ui/context_bar.h"
#include "app/ui/editor/editor.h"
#include "app/ui/main_window.h"
#include "app/ui/status_bar.h"
#include "app/util/expand_cel_canvas.h"
#include "doc/brush.h"
#include "doc/cel.h"
#include "doc/image.h"
#include "doc/layer.h"
#include "doc/mask.h"
#include "doc/sprite.h"
@ -45,12 +49,15 @@ namespace app {
using namespace ui;
//////////////////////////////////////////////////////////////////////
// For ToolLoopController
class ToolLoopImpl : public tools::ToolLoop,
public tools::ShadingOptions {
Editor* m_editor;
Context* m_context;
tools::Tool* m_tool;
Brush* m_brush;
BrushRef m_brush;
Document* m_document;
Sprite* m_sprite;
Layer* m_layer;
@ -140,18 +147,9 @@ public:
}
m_previewFilled = m_toolSettings->getPreviewFilled();
m_sprayWidth = m_toolSettings->getSprayWidth();
m_spraySpeed = m_toolSettings->getSpraySpeed();
// Create the brush
IBrushSettings* brush_settings = m_toolSettings->getBrush();
ASSERT(brush_settings != NULL);
m_brush = new Brush(
brush_settings->getType(),
brush_settings->getSize(),
brush_settings->getAngle());
m_brush = App::instance()->getMainWindow()->getContextBar()->activeBrush();
if (m_ink->isSelection())
m_useMask = false;
@ -224,7 +222,6 @@ public:
}
}
delete m_brush;
delete m_shadeTable;
if (redraw)
@ -232,7 +229,7 @@ public:
}
tools::Tool* getTool() override { return m_tool; }
Brush* getBrush() override { return m_brush; }
Brush* getBrush() override { return m_brush.get(); }
Document* getDocument() override { return m_document; }
Sprite* sprite() override { return m_sprite; }
Layer* getLayer() override { return m_layer; }
@ -391,4 +388,209 @@ tools::ToolLoop* create_tool_loop(Editor* editor, Context* context)
}
}
//////////////////////////////////////////////////////////////////////
// For preview
class PreviewToolLoopImpl : public tools::ToolLoop,
public tools::ShadingOptions {
Editor* m_editor;
Context* m_context;
tools::Tool* m_tool;
BrushRef m_brush;
Document* m_document;
Sprite* m_sprite;
Layer* m_layer;
frame_t m_frame;
ISettings* m_settings;
DocumentPreferences& m_docPref;
IToolSettings* m_toolSettings;
int m_opacity;
int m_tolerance;
bool m_contiguous;
gfx::Point m_offset;
gfx::Point m_speed;
bool m_canceled;
tools::ToolLoop::Button m_button;
tools::Ink* m_ink;
int m_primary_color;
int m_secondary_color;
gfx::Region m_dirtyArea;
tools::ShadeTable8* m_shadeTable;
Image* m_image;
tools::PointShape* m_pointShape;
public:
PreviewToolLoopImpl(
Editor* editor,
Context* context,
tools::Tool* tool,
tools::Ink* ink,
Document* document,
tools::ToolLoop::Button button,
const app::Color& primary_color,
const app::Color& secondary_color,
Image* image,
const gfx::Point& offset)
: m_editor(editor)
, m_context(context)
, m_tool(tool)
, m_document(document)
, m_sprite(editor->sprite())
, m_layer(editor->layer())
, m_frame(editor->frame())
, m_settings(m_context->settings())
, m_docPref(App::instance()->preferences().document(m_document))
, m_toolSettings(m_settings->getToolSettings(m_tool))
, m_offset(offset)
, m_canceled(false)
, m_button(button)
, m_ink(ink)
, m_primary_color(color_utils::color_for_layer(primary_color, m_layer))
, m_secondary_color(color_utils::color_for_layer(secondary_color, m_layer))
, m_shadeTable(NULL)
, m_image(image)
{
m_brush = App::instance()->getMainWindow()->getContextBar()->activeBrush();
m_opacity = m_toolSettings->getOpacity();
m_tolerance = m_toolSettings->getTolerance();
m_contiguous = m_toolSettings->getContiguous();
m_speed.x = 0;
m_speed.y = 0;
// Avoid preview for spray and flood fill like tools
m_pointShape = m_tool->getPointShape(m_button);
if (m_pointShape->isSpray()) {
m_pointShape = App::instance()->getToolBox()->getPointShapeById(
tools::WellKnownPointShapes::Brush);
}
else if (m_pointShape->isFloodFill()) {
m_pointShape = App::instance()->getToolBox()->getPointShapeById(
tools::WellKnownPointShapes::Pixel);
}
}
// IToolLoop interface
void dispose() override { }
tools::Tool* getTool() override { return m_tool; }
Brush* getBrush() override { return m_brush.get(); }
Document* getDocument() override { return m_document; }
Sprite* sprite() override { return m_sprite; }
Layer* getLayer() override { return m_layer; }
frame_t getFrame() override { return m_frame; }
const Image* getSrcImage() override { return m_image; }
Image* getDstImage() override { return m_image; }
void validateSrcImage(const gfx::Region& rgn) override { }
void validateDstImage(const gfx::Region& rgn) override { }
void invalidateDstImage() override { }
void invalidateDstImage(const gfx::Region& rgn) override { }
void copyValidDstToSrcImage(const gfx::Region& rgn) override { }
RgbMap* getRgbMap() override { return m_sprite->rgbMap(m_frame); }
bool useMask() override { return false; // m_useMask;
}
Mask* getMask() override { return nullptr; // m_mask;
}
void setMask(Mask* newMask) override { }
gfx::Point getMaskOrigin() override { return gfx::Point(0, 0); // m_maskOrigin;
}
const render::Zoom& zoom() override { return m_editor->zoom(); }
ToolLoop::Button getMouseButton() override { return m_button; }
int getPrimaryColor() override { return m_primary_color; }
void setPrimaryColor(int color) override { m_primary_color = color; }
int getSecondaryColor() override { return m_secondary_color; }
void setSecondaryColor(int color) override { m_secondary_color = color; }
int getOpacity() override { return m_opacity; }
int getTolerance() override { return m_tolerance; }
bool getContiguous() override { return m_contiguous; }
SelectionMode getSelectionMode() override { return m_editor->getSelectionMode(); }
ISettings* settings() override { return m_settings; }
filters::TiledMode getTiledMode() override { return m_docPref.tiled.mode(); }
bool getGridVisible() override { return m_docPref.grid.visible(); }
bool getSnapToGrid() override { return m_docPref.grid.snap(); }
gfx::Rect getGridBounds() override { return m_docPref.grid.bounds(); }
bool getFilled() override { return false; }
bool getPreviewFilled() override { return false; }
int getSprayWidth() override { return 0; }
int getSpraySpeed() override { return 0; }
gfx::Point getOffset() override { return m_offset; }
void setSpeed(const gfx::Point& speed) override { m_speed = speed; }
gfx::Point getSpeed() override { return m_speed; }
tools::Ink* getInk() override { return m_ink; }
tools::Controller* getController() override { return m_tool->getController(m_button); }
tools::PointShape* getPointShape() override { return m_pointShape; }
tools::Intertwine* getIntertwine() override { return m_tool->getIntertwine(m_button); }
tools::TracePolicy getTracePolicy() override { return m_tool->getTracePolicy(m_button); }
tools::ShadingOptions* getShadingOptions() override { return this; }
void cancel() override { }
bool isCanceled() override { return true; }
gfx::Point screenToSprite(const gfx::Point& screenPoint) override {
return m_editor->screenToEditor(screenPoint);
}
gfx::Region& getDirtyArea() override {
return m_dirtyArea;
}
void updateDirtyArea() override {
}
void updateStatusBar(const char* text) override {
StatusBar::instance()->setStatusText(0, text);
}
// ShadingOptions implementation
tools::ShadeTable8* getShadeTable() override {
if (m_shadeTable == NULL) {
app::ColorSwatches* colorSwatches = m_settings->getColorSwatches();
ASSERT(colorSwatches != NULL);
m_shadeTable = new tools::ShadeTable8(*colorSwatches,
tools::kRotateShadingMode);
}
return m_shadeTable;
}
};
tools::ToolLoop* create_tool_loop_preview(
Editor* editor, Context* context, Image* image,
const gfx::Point& offset)
{
tools::Tool* current_tool = editor->getCurrentEditorTool();
tools::Ink* current_ink = editor->getCurrentEditorInk();
if (!current_tool || !current_ink)
return NULL;
Layer* layer = editor->layer();
if (!layer ||
!layer->isVisible() ||
!layer->isEditable()) {
return nullptr;
}
// Get fg/bg colors
ColorBar* colorbar = ColorBar::instance();
app::Color fg = colorbar->getFgColor();
app::Color bg = colorbar->getBgColor();
if (!fg.isValid() || !bg.isValid())
return nullptr;
// Create the new tool loop
try {
return new PreviewToolLoopImpl(
editor, context,
current_tool,
current_ink,
editor->document(),
tools::ToolLoop::Left,
fg, bg, image, offset);
}
catch (const std::exception&) {
return nullptr;
}
}
//////////////////////////////////////////////////////////////////////
} // namespace app

View File

@ -9,6 +9,13 @@
#define APP_UI_EDITOR_TOOL_LOOP_IMPL_H_INCLUDED
#pragma once
#include "doc/image_ref.h"
#include "gfx/fwd.h"
namespace doc {
class Image;
}
namespace app {
class Context;
class Editor;
@ -17,7 +24,12 @@ namespace app {
class ToolLoop;
}
tools::ToolLoop* create_tool_loop(Editor* editor, Context* context);
tools::ToolLoop* create_tool_loop(
Editor* editor, Context* context);
tools::ToolLoop* create_tool_loop_preview(
Editor* editor, Context* context, doc::Image* image,
const gfx::Point& offset);
} // namespace app

View File

@ -129,13 +129,6 @@ namespace app {
PART_TARGET_FRAMES_LAYERS,
PART_TARGET_FRAMES_LAYERS_SELECTED,
PART_BRUSH_CIRCLE,
PART_BRUSH_CIRCLE_SELECTED,
PART_BRUSH_SQUARE,
PART_BRUSH_SQUARE_SELECTED,
PART_BRUSH_LINE,
PART_BRUSH_LINE_SELECTED,
PART_SCALE_ARROW_1,
PART_SCALE_ARROW_2,
PART_SCALE_ARROW_3,

View File

@ -238,12 +238,6 @@ SkinTheme::SkinTheme()
sheet_mapping["target_layers_selected"] = PART_TARGET_LAYERS_SELECTED;
sheet_mapping["target_frames_layers"] = PART_TARGET_FRAMES_LAYERS;
sheet_mapping["target_frames_layers_selected"] = PART_TARGET_FRAMES_LAYERS_SELECTED;
sheet_mapping["brush_circle"] = PART_BRUSH_CIRCLE;
sheet_mapping["brush_circle_selected"] = PART_BRUSH_CIRCLE_SELECTED;
sheet_mapping["brush_square"] = PART_BRUSH_SQUARE;
sheet_mapping["brush_square_selected"] = PART_BRUSH_SQUARE_SELECTED;
sheet_mapping["brush_line"] = PART_BRUSH_LINE;
sheet_mapping["brush_line_selected"] = PART_BRUSH_LINE_SELECTED;
sheet_mapping["scale_arrow_1"] = PART_SCALE_ARROW_1;
sheet_mapping["scale_arrow_2"] = PART_SCALE_ARROW_2;
sheet_mapping["scale_arrow_3"] = PART_SCALE_ARROW_3;

View File

@ -24,8 +24,13 @@ using namespace doc;
Image* new_image_from_mask(const Site& site)
{
const Sprite* srcSprite = site.sprite();
const Mask* srcMask = static_cast<const app::Document*>(site.document())->mask();
return new_image_from_mask(site, srcMask);
}
doc::Image* new_image_from_mask(const doc::Site& site, const doc::Mask* srcMask)
{
const Sprite* srcSprite = site.sprite();
const Image* srcMaskBitmap = srcMask->bitmap();
const gfx::Rect& srcBounds = srcMask->bounds();
int x, y, u, v, getx, gety;
@ -38,7 +43,7 @@ Image* new_image_from_mask(const Site& site)
dst = Image::create(srcSprite->pixelFormat(), srcBounds.w, srcBounds.h);
if (!dst)
return NULL;
return nullptr;
// Clear the new image
dst->setMaskColor(src ? src->maskColor(): srcSprite->transparentColor());

View File

@ -11,12 +11,14 @@
namespace doc {
class Image;
class Mask;
class Site;
}
namespace app {
doc::Image* new_image_from_mask(const doc::Site& site);
doc::Image* new_image_from_mask(const doc::Site& site, const doc::Mask* mask);
} // namespace app

View File

@ -18,6 +18,7 @@ add_library(doc-lib
cel_io.cpp
cels_range.cpp
color_scales.cpp
compressed_image.cpp
context.cpp
conversion_she.cpp
document.cpp

View File

@ -24,7 +24,7 @@ Brush::Brush()
m_type = kCircleBrushType;
m_size = 1;
m_angle = 0;
m_image = NULL;
m_pattern = BrushPattern::DEFAULT;
regenerate();
}
@ -34,7 +34,7 @@ Brush::Brush(BrushType type, int size, int angle)
m_type = type;
m_size = size;
m_angle = angle;
m_image = NULL;
m_pattern = BrushPattern::DEFAULT;
regenerate();
}
@ -44,6 +44,9 @@ 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_pattern = brush.m_pattern;
m_patternOrigin = brush.m_patternOrigin;
regenerate();
}
@ -56,7 +59,10 @@ Brush::~Brush()
void Brush::setType(BrushType type)
{
m_type = type;
regenerate();
if (m_type != kImageBrushType)
regenerate();
else
clean();
}
void Brush::setSize(int size)
@ -71,15 +77,19 @@ void Brush::setAngle(int angle)
regenerate();
}
void Brush::setImage(const Image* image)
{
m_type = kImageBrushType;
m_image.reset(Image::createCopy(image));
m_bounds = gfx::Rect(
-m_image.get()->width()/2, -m_image.get()->height()/2,
m_image.get()->width(), m_image.get()->height());
}
// Cleans the brush's data (image and region).
void Brush::clean()
{
if (m_image) {
delete m_image;
m_image = NULL;
}
m_scanline.clear();
m_image.reset();
}
static void algo_hline(int x1, int y, int x2, void *data)
@ -98,23 +108,23 @@ void Brush::regenerate()
if (m_type == kSquareBrushType && m_angle != 0 && m_size > 2)
size = (int)std::sqrt((double)2*m_size*m_size)+2;
m_image = Image::create(IMAGE_BITMAP, size, size);
m_image.reset(Image::create(IMAGE_BITMAP, size, size));
if (size == 1) {
clear_image(m_image, BitmapTraits::max_value);
clear_image(m_image.get(), BitmapTraits::max_value);
}
else {
clear_image(m_image, BitmapTraits::min_value);
clear_image(m_image.get(), BitmapTraits::min_value);
switch (m_type) {
case kCircleBrushType:
fill_ellipse(m_image, 0, 0, size-1, size-1, BitmapTraits::max_value);
fill_ellipse(m_image.get(), 0, 0, size-1, size-1, BitmapTraits::max_value);
break;
case kSquareBrushType:
if (m_angle == 0 || size <= 2) {
clear_image(m_image, BitmapTraits::max_value);
clear_image(m_image.get(), BitmapTraits::max_value);
}
else {
double a = PI * m_angle / 180;
@ -131,7 +141,7 @@ void Brush::regenerate()
int y4 = int(y3 - d*sin(a+PI));
int points[8] = { x1, y1, x2, y2, x3, y3, x4, y4 };
doc::algorithm::polygon(4, points, m_image, algo_hline);
doc::algorithm::polygon(4, points, m_image.get(), algo_hline);
}
break;
@ -144,26 +154,7 @@ void Brush::regenerate()
int x2 = int(x1 + d*cos(a));
int y2 = int(y1 - d*sin(a));
draw_line(m_image, x1, y1, x2, y2, BitmapTraits::max_value);
break;
}
}
}
m_scanline.resize(m_image->height());
for (int y=0; y<m_image->height(); y++) {
m_scanline[y].state = false;
for (int x=0; x<m_image->width(); x++) {
if (get_pixel(m_image, x, y)) {
m_scanline[y].x1 = x;
for (; x<m_image->width(); x++)
if (!get_pixel(m_image, x, y))
break;
m_scanline[y].x2 = x-1;
m_scanline[y].state = true;
draw_line(m_image.get(), x1, y1, x2, y2, BitmapTraits::max_value);
break;
}
}

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2001-2014 David Capello
// Copyright (c) 2001-2015 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -8,19 +8,16 @@
#define DOC_BRUSH_H_INCLUDED
#pragma once
#include "doc/brush_pattern.h"
#include "doc/brush_type.h"
#include "doc/image_ref.h"
#include "gfx/point.h"
#include "gfx/rect.h"
#include "doc/brush_type.h"
#include <vector>
namespace doc {
class Image;
struct BrushScanline {
int state, x1, x2;
};
class Brush {
public:
static const int kMinBrushSize = 1;
@ -34,27 +31,39 @@ namespace doc {
BrushType type() const { return m_type; }
int size() const { return m_size; }
int angle() const { return m_angle; }
Image* image() { return m_image; }
const std::vector<BrushScanline>& scanline() const { return m_scanline; }
Image* image() const { return m_image.get(); }
BrushPattern pattern() const { return m_pattern; }
gfx::Point patternOrigin() const { return m_patternOrigin; }
const gfx::Rect& bounds() const { return m_bounds; }
void setType(BrushType type);
void setSize(int size);
void setAngle(int angle);
void setImage(const Image* image);
void setPattern(BrushPattern pattern) {
m_pattern = pattern;
}
void setPatternOrigin(const gfx::Point& patternOrigin) {
m_patternOrigin = patternOrigin;
}
private:
void clean();
void regenerate();
BrushType m_type; // Type of brush
BrushType m_type; // Type of brush
int m_size; // Size (diameter)
int m_angle; // Angle in degrees 0-360
Image* m_image; // Image of the brush
std::vector<BrushScanline> m_scanline;
ImageRef m_image; // Image of the brush
gfx::Rect m_bounds;
BrushPattern m_pattern; // How the image should be replicated
gfx::Point m_patternOrigin; // From what position the brush was taken
};
typedef base::SharedPtr<Brush> BrushRef;
} // namespace doc
#endif

22
src/doc/brush_pattern.h Normal file
View File

@ -0,0 +1,22 @@
// Aseprite Document Library
// Copyright (c) 2001-2015 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef DOC_BRUSH_PATTERN_H_INCLUDED
#define DOC_BRUSH_PATTERN_H_INCLUDED
#pragma once
namespace doc {
enum class BrushPattern {
DEFAULT = 0,
ALIGNED_TO_SRC = 0,
ALIGNED_TO_DST = 1,
PAINT_BRUSH = 2,
};
} // namespace doc
#endif

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2001-2014 David Capello
// Copyright (c) 2001-2015 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -14,9 +14,10 @@ namespace doc {
kCircleBrushType = 0,
kSquareBrushType = 1,
kLineBrushType = 2,
kImageBrushType = 3,
kFirstBrushType = kCircleBrushType,
kLastBrushType = kLineBrushType,
kLastBrushType = kImageBrushType,
};
} // namespace doc

21
src/doc/brushes.h Normal file
View File

@ -0,0 +1,21 @@
// Aseprite Document Library
// Copyright (c) 2001-2015 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef DOC_BRUSHES_H_INCLUDED
#define DOC_BRUSHES_H_INCLUDED
#pragma once
#include "doc/brush.h"
#include <vector>
namespace doc {
typedef std::vector<BrushRef> Brushes;
} // namespace doc
#endif

View File

@ -0,0 +1,48 @@
// Aseprite Document Library
// Copyright (c) 2001-2015 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/compressed_image.h"
#include "doc/primitives.h"
namespace doc {
CompressedImage::CompressedImage(const Image* image, bool diffColors)
: m_image(image)
{
color_t c1, c2, mask = image->maskColor();
for (int y=0; y<image->height(); ++y) {
Scanline scanline(y);
for (int x=0; x<image->width(); ) {
c1 = get_pixel(image, x, y);
if (c1 != mask) {
scanline.color = c1;
scanline.x = x;
for (++x; x<image->width(); ++x) {
c2 = get_pixel(image, x, y);
if ((diffColors && c1 != c2) ||
(!diffColors && c2 == mask))
break;
}
scanline.w = x - scanline.x;
m_scanlines.push_back(scanline);
}
else
++x;
}
}
}
} // namespace doc

View File

@ -0,0 +1,49 @@
// Aseprite Document Library
// Copyright (c) 2001-2015 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef DOC_COMPRESSED_IMAGE_H_INCLUDED
#define DOC_COMPRESSED_IMAGE_H_INCLUDED
#pragma once
#include "doc/color.h"
#include "doc/image.h"
#include <vector>
namespace doc {
class CompressedImage {
public:
struct Scanline {
int x, y, w;
color_t color;
Scanline(int y) : x(0), y(y), w(0), color(0) { }
};
typedef std::vector<Scanline> Scanlines;
typedef Scanlines::const_iterator const_iterator;
// If diffColors is true, it generates one Scanline instance for
// each different color. If it's false, it generates a scanline
// for each row of consecutive pixels different than the mask
// color.
CompressedImage(const Image* image, bool diffColors);
const_iterator begin() const { return m_scanlines.begin(); }
const_iterator end() const { return m_scanlines.end(); }
PixelFormat pixelFormat() const { return m_image->pixelFormat(); }
int width() const { return m_image->width(); }
int height() const { return m_image->height(); }
private:
const Image* m_image;
Scanlines m_scanlines;
};
} // namespace doc
#endif

View File

@ -40,33 +40,6 @@ void put_pixel(Image* image, int x, int y, color_t color)
image->putPixel(x, y, color);
}
void draw_brush(Image* image, Brush* brush, int x, int y, color_t fg, color_t bg)
{
ASSERT(image);
ASSERT(brush);
Image* brush_image = brush->image();
const gfx::Rect& brushBounds = brush->bounds();
x += brushBounds.x;
y += brushBounds.y;
if (fg == bg) {
fill_rect(image, x, y, x+brushBounds.w-1, y+brushBounds.h-1, bg);
}
else {
int u, v;
for (v=0; v<brushBounds.h; v++) {
for (u=0; u<brushBounds.w; u++) {
if (get_pixel(brush_image, u, v))
put_pixel(image, x+u, y+v, fg);
else
put_pixel(image, x+u, y+v, bg);
}
}
}
}
void clear_image(Image* image, color_t color)
{
ASSERT(image);

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2001-2014 David Capello
// Copyright (c) 2001-2015 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -19,7 +19,6 @@ namespace doc {
color_t get_pixel(const Image* image, int x, int y);
void put_pixel(Image* image, int x, int y, color_t c);
void draw_brush(Image* image, Brush* brush, int x, int y, color_t fg, color_t bg);
void clear_image(Image* image, color_t bg);

View File

@ -186,6 +186,7 @@ void gen_pref_header(TiXmlDocument* doc, const std::string& inputFn)
<< "#include \"app/color.h\"\n"
<< "#include \"app/pref/option.h\"\n"
<< "#include \"doc/anidir.h\"\n"
<< "#include \"doc/brush_pattern.h\"\n"
<< "#include \"doc/frame.h\"\n"
<< "#include \"gfx/rect.h\"\n"
<< "#include \"filters/tiled_mode.h\"\n"

27
src/render/extra_type.h Normal file
View File

@ -0,0 +1,27 @@
// Aseprite Render Library
// Copyright (c) 2001-2015 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef RENDER_EXTRA_TYPE_H_INCLUDED
#define RENDER_EXTRA_TYPE_H_INCLUDED
#pragma once
namespace render {
enum class ExtraType {
NONE,
// The extra cel indicates a "patch" for the current layer/frame
// given in Render::setExtraImage()
PATCH,
// The extra cel indicates an extra composition for the current
// layer/frame.
COMPOSITE,
};
} // namespace render
#endif

View File

@ -11,6 +11,8 @@
#include "render/render.h"
#include "doc/doc.h"
#include "gfx/clip.h"
#include "gfx/region.h"
namespace render {
@ -320,6 +322,7 @@ Render::Render()
: m_sprite(NULL)
, m_currentLayer(NULL)
, m_currentFrame(0)
, m_extraType(ExtraType::NONE)
, m_extraCel(NULL)
, m_extraImage(NULL)
, m_bgType(BgType::TRANSPARENT)
@ -362,10 +365,12 @@ void Render::setPreviewImage(const Layer* layer, frame_t frame, Image* image)
}
void Render::setExtraImage(
ExtraType type,
const Cel* cel, const Image* image, int blendMode,
const Layer* currentLayer,
frame_t currentFrame)
{
m_extraType = type;
m_extraCel = cel;
m_extraImage = image;
m_extraBlendMode = blendMode;
@ -380,6 +385,7 @@ void Render::removePreviewImage()
void Render::removeExtraImage()
{
m_extraType = ExtraType::NONE;
m_extraCel = NULL;
}
@ -428,7 +434,8 @@ void Render::renderLayer(
Image* dstImage,
const Layer* layer,
frame_t frame,
const gfx::Clip& area)
const gfx::Clip& area,
int blend_mode)
{
m_sprite = layer->sprite();
@ -442,7 +449,7 @@ void Render::renderLayer(
m_globalOpacity = 255;
renderLayer(layer, dstImage, area,
frame, Zoom(1, 1), scaled_func,
true, true, -1);
true, true, blend_mode);
}
void Render::renderSprite(
@ -603,6 +610,26 @@ void Render::renderLayer(
if (!layer->isVisible())
return;
gfx::Rect extraArea;
bool drawExtra = (m_extraCel &&
m_extraImage &&
layer == m_currentLayer &&
frame == m_currentFrame);
if (drawExtra) {
extraArea = gfx::Rect(
m_extraCel->x(),
m_extraCel->y(),
m_extraImage->width(),
m_extraImage->height());
extraArea = zoom.apply(extraArea);
if (zoom.scale() < 1.0) {
extraArea.w--;
extraArea.h--;
}
if (extraArea.w < 1) extraArea.w = 1;
if (extraArea.h < 1) extraArea.h = 1;
}
switch (layer->type()) {
case ObjectType::LayerImage: {
@ -612,6 +639,7 @@ void Render::renderLayer(
const Cel* cel = layer->cel(frame);
if (cel != NULL) {
Palette* pal = m_sprite->palette(frame);
Image* src_image;
// Is the 'm_previewImage' set to be used with this layer?
@ -633,14 +661,34 @@ void Render::renderLayer(
ASSERT(src_image->maskColor() == m_sprite->transparentColor());
renderCel(image, src_image,
m_sprite->palette(frame),
cel, area, scaled_func,
output_opacity,
int layer_blend_mode =
(blend_mode < 0 ?
static_cast<const LayerImage*>(layer)->getBlendMode():
blend_mode),
zoom);
static_cast<const LayerImage*>(layer)->getBlendMode():
blend_mode);
// Draw parts outside the "m_extraCel" area
if (drawExtra && m_extraType == ExtraType::PATCH) {
gfx::Region originalAreas(area.srcBounds());
originalAreas.createSubtraction(
originalAreas, gfx::Region(extraArea));
for (auto rc : originalAreas) {
renderCel(
image, src_image, pal,
cel, gfx::Clip(area.dst.x+rc.x-area.src.x,
area.dst.y+rc.y-area.src.y, rc), scaled_func,
output_opacity,
layer_blend_mode, zoom);
}
}
// Draw the whole cel
else {
renderCel(
image, src_image, pal,
cel, area, scaled_func,
output_opacity,
layer_blend_mode, zoom);
}
}
}
break;
@ -663,14 +711,16 @@ void Render::renderLayer(
}
// Draw extras
if (m_extraCel &&
m_extraImage &&
layer == m_currentLayer &&
frame == m_currentFrame) {
if (drawExtra && m_extraType != ExtraType::NONE) {
if (m_extraCel->opacity() > 0) {
renderCel(image, m_extraImage,
renderCel(
image, m_extraImage,
m_sprite->palette(frame),
m_extraCel, area, scaled_func,
m_extraCel,
gfx::Clip(area.dst.x+extraArea.x-area.src.x,
area.dst.y+extraArea.y-area.src.y,
extraArea),
scaled_func,
m_extraCel->opacity(),
m_extraBlendMode, zoom);
}

View File

@ -1,5 +1,5 @@
// Aseprite Render Library
// Copyright (c) 2001-2014 David Capello
// Copyright (c) 2001-2015 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -13,6 +13,7 @@
#include "doc/pixel_format.h"
#include "gfx/fwd.h"
#include "gfx/size.h"
#include "render/extra_type.h"
#include "render/zoom.h"
namespace gfx {
@ -45,7 +46,7 @@ namespace render {
class Render {
public:
Render();
// Background configuration
void setBgType(BgType type);
void setBgZoom(bool state);
@ -61,6 +62,7 @@ namespace render {
// Sets an extra cel/image to be drawn after the current
// layer/frame.
void setExtraImage(
ExtraType type,
const Cel* cel, const Image* image, int blendMode,
const Layer* currentLayer,
frame_t currentFrame);
@ -90,7 +92,8 @@ namespace render {
Image* dstImage,
const Layer* layer,
frame_t frame,
const gfx::Clip& area);
const gfx::Clip& area,
int blend_mode = -1);
// Main function used to render the sprite. Draws the given sprite
// frame in a new image and return it. Note: zoomedRect must have
@ -143,6 +146,7 @@ namespace render {
const Sprite* m_sprite;
const Layer* m_currentLayer;
frame_t m_currentFrame;
ExtraType m_extraType;
const Cel* m_extraCel;
const Image* m_extraImage;
int m_extraBlendMode;

View File

@ -161,6 +161,7 @@ namespace she {
void setDrawMode(DrawMode mode, int param) {
switch (mode) {
case DrawMode::Solid: checked_mode(-1); break;
case DrawMode::Xor: xor_mode(TRUE); break;
case DrawMode::Checked: checked_mode(param); break;
}
}

View File

@ -1,5 +1,5 @@
// SHE library
// Copyright (C) 2012-2013 David Capello
// Copyright (C) 2012-2013, 2015 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -16,7 +16,8 @@ namespace she {
enum class DrawMode {
Solid,
Checked
Checked,
Xor
};
class Surface {

View File

@ -67,6 +67,9 @@ void Graphics::setDrawMode(DrawMode mode, int param)
case DrawMode::Solid:
m_surface->setDrawMode(she::DrawMode::Solid);
break;
case DrawMode::Xor:
m_surface->setDrawMode(she::DrawMode::Xor);
break;
case DrawMode::Checked:
m_surface->setDrawMode(she::DrawMode::Checked, param);
break;

View File

@ -33,6 +33,7 @@ namespace ui {
public:
enum class DrawMode {
Solid,
Xor,
Checked,
};

View File

@ -1,5 +1,5 @@
// Aseprite UI Library
// Copyright (C) 2001-2013 David Capello
// Copyright (C) 2001-2013, 2015 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -47,6 +47,13 @@ void TooltipManager::addTooltipFor(Widget* widget, const std::string& text, int
m_tips[widget] = TipInfo(text, arrowAlign);
}
void TooltipManager::removeTooltipFor(Widget* widget)
{
auto it = m_tips.find(widget);
if (it != m_tips.end())
m_tips.erase(it);
}
bool TooltipManager::onProcessMessage(Message* msg)
{
switch (msg->type()) {

View File

@ -1,5 +1,5 @@
// Aseprite UI Library
// Copyright (C) 2001-2013 David Capello
// Copyright (C) 2001-2013, 2015 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -8,6 +8,7 @@
#define UI_TOOLTIPS_H_INCLUDED
#pragma once
#include "base/unique_ptr.h"
#include "ui/base.h"
#include "ui/popup_window.h"
#include "ui/window.h"
@ -25,6 +26,7 @@ namespace ui {
~TooltipManager();
void addTooltipFor(Widget* widget, const std::string& text, int arrowAlign = 0);
void removeTooltipFor(Widget* widget);
protected:
bool onProcessMessage(Message* msg) override;