Merge branch 'master' into tilemap-editor

This commit is contained in:
David Capello 2020-06-01 16:21:32 -03:00
commit a10efb187b
37 changed files with 743 additions and 345 deletions

View File

@ -3,7 +3,6 @@ language: cpp
matrix:
include:
- os: osx
osx_image: xcode9
env:
- ENABLE_UI=ON
- MATRIX_EVAL="wget https://github.com/ninja-build/ninja/releases/download/v1.8.2/ninja-mac.zip && unzip ninja-mac.zip && export PATH=$PWD:$PATH"

View File

@ -219,7 +219,7 @@ add_definitions(-DPNG_NO_MMX_CODE) # Do not use MMX optimizations in PNG code
# libwebp
if(WITH_WEBP_SUPPORT)
set(WEBP_LIBRARIES webp)
set(WEBP_LIBRARIES webp webpdemux libwebpmux)
set(WEBP_INCLUDE_DIR ${LIBWEBP_DIR}/src)
include_directories(${WEBP_INCLUDE_DIR})
endif()

View File

@ -93,7 +93,7 @@ You can fork the GitHub repository using the Fork button at
The Pull Requests (PR) systems works in this way:
1. First of all you will need to sign our
[Contributor License Agreement](https://github.com/aseprite/opensource/blob/master/sign-cla.md#sign-the-cla) (CLA).
[Contributor License Agreement](https://github.com/aseprite/sourcecode/blob/master/sign-cla.md#sign-the-cla) (CLA).
1. Then you can start working on Aseprite. Create a new branch from `master`, e.g. `fix-8` to fix the issue 8.
Check this guide about [how to name your branch](https://github.com/agis/git-style-guide#branches).
1. Start working on that new branch, and push your commits to your fork.

View File

@ -7,21 +7,24 @@
## Introduction
**Aseprite** is a program to create animated sprites. Its main
features are:
**Aseprite** is a program to create animated sprites. Its main features are:
* Sprites are composed of [**layers** & **frames**](http://www.aseprite.org/docs/timeline/) (as separated concepts).
* Supported [color modes](http://www.aseprite.org/docs/color/): **RGBA**, **Indexed** (palettes up to 256
colors), and Grayscale.
* Load/save a sequence of **PNG** files and **GIF** animations (and
FLC, FLI, JPG, BMP, PCX, TGA).
* Export/import animations to/from **Sprite Sheets**.
* **Tiled** drawing mode, useful to draw **patterns** and textures.
* **Undo/Redo** for every operation.
* Real-time **animation preview**.
* [**Multiple editors**](http://www.aseprite.org/docs/workspace/#drag-and-drop-tabs) support.
* Pixel-art specific tools like filled **Contour**, **Polygon**, [**Shading**](http://www.aseprite.org/docs/shading/) mode, etc.
* [**Onion skinning**](https://www.aseprite.org/docs/animation/#onion-skinning)
* Sprites are composed of [layers & frames](https://www.aseprite.org/docs/timeline/) as separated concepts.
* Support for [color profiles](https://www.aseprite.org/docs/color-profile/) and different [color modes](https://www.aseprite.org/docs/color-mode/): RGBA, Indexed (palettes up to 256 colors), Grayscale.
* [Animation facilities](https://www.aseprite.org/docs/animation/), with real-time [preview](https://www.aseprite.org/docs/preview-window/) and [onion skinning](https://www.aseprite.org/docs/onion-skinning/).
* [Export/import](https://www.aseprite.org/docs/exporting/) animations to/from [sprite sheets](https://www.aseprite.org/docs/sprite-sheet/), GIF files, or sequence of PNG files (and FLC, FLI, JPG, BMP, PCX, TGA).
* [Multiple editors](https://www.aseprite.org/docs/workspace/#drag-and-drop-tabs) support.
* [Layer groups](https://imgur.com/x3OKkGj) for organizing your work, and [reference layers](https://twitter.com/aseprite/status/806889204601016325) for rotoscoping.
* Pixel-art specific tools like [Pixel Perfect freehand mode](https://imgur.com/0fdlNau), [Shading ink](https://www.aseprite.org/docs/shading/), [Custom Brushes](https://twitter.com/aseprite/status/1196883990080344067), [Outlines](https://twitter.com/aseprite/status/1126548469865431041), [Wide Pixels](https://imgur.com/1yZKUcs), etc.
* Other special drawing tools like [Pressure sensitivity](https://twitter.com/aseprite/status/1253770784708886533), [Symmetry Tool](https://twitter.com/aseprite/status/659709226747625472), [Stroke and Fill](https://imgur.com/7JZQ81o) selection, [Gradients](https://twitter.com/aseprite/status/1126549217856622597).
* [Tiled mode](https://twitter.com/pixel__toast/status/1132079817736695808) useful to draw patterns and textures.
* [Transform multiple frames/layers](https://twitter.com/aseprite/status/1170007034651172866) at the same time.
* [Lua scripting capabilities](https://www.aseprite.org/docs/scripting/).
* [CLI - Command Line Interface](https://www.aseprite.org/docs/cli/) to automatize tasks.
* [Quick Reference / Cheat Sheet](https://www.aseprite.org/quickref/) keyboard shortcuts ([customizable keys](https://imgur.com/rvAUxyF) and [mouse wheel](https://imgur.com/oNqFqVb)).
* [Reopen closed files](https://twitter.com/aseprite/status/1202641475256881153) and [recover data](https://www.aseprite.org/docs/data-recovery/) in case of crash.
* Undo/Redo for every operation and support for [non-linear undo](https://imgur.com/9I42fZK).
* [More features & tips](https://twitter.com/aseprite/status/1124442198651678720)
## Issues
@ -77,7 +80,7 @@ It tries to replicate some pixel-art algorithms:
* [Pixel perfect drawing algorithm](http://deepnight.net/pixel-perfect-drawing/) by [Sébastien Bénard](https://twitter.com/deepnightfr) and [Carduus](https://twitter.com/CarduusHimself/status/420554200737935361).
Thanks to [third-party open source projects](docs/LICENSES.md), to
[contributors](http://www.aseprite.org/contributors/), and all the
[contributors](https://www.aseprite.org/contributors/), and all the
people who have contributed ideas, patches, bugs report, feature
requests, donations, and help me to develop Aseprite.
@ -95,11 +98,11 @@ This program is distributed under three different licenses:
[observable](https://github.com/aseprite/observable),
[ui](src/ui), etc.).
2. You can request a special
[educational license](http://www.aseprite.org/faq/#is-there-an-educational-license)
[educational license](https://www.aseprite.org/faq/#is-there-an-educational-license)
in case you are a teacher in an educational institution and want to
use Aseprite in your classroom (in-situ).
3. Steam releases are distributed under the terms of the
[Steam Subscriber Agreement](http://store.steampowered.com/subscriber_agreement/).
You can get more information about Aseprite license in the
[FAQ](http://www.aseprite.org/faq/#licensing-&-commercial).
[FAQ](https://www.aseprite.org/faq/#licensing-&-commercial).

View File

@ -0,0 +1,34 @@
GIMP Palette
#
0 0 0 Untitled
24 13 47 Untitled
53 54 88 Untitled
104 107 114 Untitled
139 151 182 Untitled
197 205 219 Untitled
255 255 255 Untitled
94 233 233 Untitled
40 144 220 Untitled
24 49 167 Untitled
5 50 57 Untitled
0 95 65 Untitled
8 178 59 Untitled
71 246 65 Untitled
232 255 117 Untitled
251 190 130 Untitled
222 151 81 Untitled
182 104 49 Untitled
138 73 38 Untitled
70 28 20 Untitled
30 9 13 Untitled
114 13 13 Untitled
129 55 4 Untitled
218 36 36 Untitled
239 110 16 Untitled
236 171 17 Untitled
236 233 16 Untitled
247 141 141 Untitled
249 78 109 Untitled
193 36 88 Untitled
132 18 82 Untitled
61 8 59 Untitled

View File

@ -16,7 +16,8 @@
{ "id": "EDG32", "path": "./edg32.gpl" },
{ "id": "EDG8", "path": "./edg8.gpl" },
{ "id": "EN4", "path": "./en4.gpl" },
{ "id": "ENOS16", "path": "./enos16.gpl" }
{ "id": "ENOS16", "path": "./enos16.gpl" },
{ "id": "HEPT32", "path": "./hept32.gpl" }
]
}
}

View File

@ -184,6 +184,9 @@
<option id="font" type="std::string" />
<option id="mini_font" type="std::string" />
</section>
<section id="tablet" text="Tablet">
<option id="api" type="std::string" />
</section>
<section id="experimental" text="Experimental">
<option id="new_render_engine" type="bool" default="true" />
<option id="new_blend" type="bool" default="true" />

View File

@ -1056,6 +1056,7 @@ skip = &Skip
title = Preferences
section_general = General
section_files = Files
section_tablet = Tablet
section_alerts = Alerts
section_color = Color
section_editor = Editor
@ -1135,6 +1136,9 @@ clear_recent_files = Clear
clear_recent_files_tooltip = Clear the list of recent files and folders
locate_file = Locate Configuration File
locate_crash_folder = Locate Crash Folder
tablet_api_windows_pointer = Windows 8/10 Pointer API (Windows Ink)
tablet_api_wintab_system = Wintab
tablet_api_wintab_direct = Wintab (direct packet processing)
wheel_zoom = Zoom with scroll wheel
slide_zoom = Zoom sliding two fingers up or down
zoom_from_center_with_wheel = Zoom from center with scroll wheel
@ -1302,12 +1306,9 @@ native_clipboard = Use native clipboard
native_file_dialog = Use native file dialog
one_finger_as_mouse_movement = Interpret one finger as mouse movement
one_finger_as_mouse_movement_tooltip = <<<END
Only for Windows 8/10 tablets: Interprets one finger as mouse movement
Only for Windows 8/10 Pointer API: Interprets one finger as mouse movement
and two fingers as pan/scroll. Uncheck this to use the old behavior:
One finger pans/scrolls.
--
Note: This option is available just to get the old behavior but
will be removed in future versions.
END
load_wintab_driver = Load wintab32 library
load_wintab_driver_tooltip = <<<END

View File

@ -8,6 +8,7 @@
<view maxsize="true">
<listbox id="section_listbox">
<listitem text="@.section_general" value="section_general" />
<listitem text="@.section_tablet" value="section_tablet" />
<listitem text="@.section_files" value="section_files" />
<listitem text="@.section_color" value="section_color" />
<listitem text="@.section_alerts" value="section_alerts" />
@ -73,6 +74,25 @@
<link id="locate_crash_folder" text="@.locate_crash_folder" />
</vbox>
<!-- Tablet -->
<vbox id="section_tablet">
<separator text="@.section_tablet" horizontal="true" />
<radio id="tablet_api_windows_pointer" text="@.tablet_api_windows_pointer" group="1" />
<radio id="tablet_api_wintab_system" text="@.tablet_api_wintab_system" group="1" />
<radio id="tablet_api_wintab_direct" text="@.tablet_api_wintab_direct" group="1" />
<separator horizontal="true" />
<check id="one_finger_as_mouse_movement"
text="@.one_finger_as_mouse_movement"
tooltip="@.one_finger_as_mouse_movement_tooltip"
pref="experimental.one_finger_as_mouse_movement" />
<hbox>
<check id="load_wintab_driver"
text="@.load_wintab_driver"
tooltip="@.load_wintab_driver_tooltip" />
<link text="@.wintab_more_info" url="https://www.aseprite.org/docs/wintab/" />
</hbox>
</vbox>
<!-- Files -->
<vbox id="section_files">
<separator text="@.section_files" horizontal="true" />
@ -473,15 +493,10 @@
</hbox>
<check id="native_clipboard" text="@.native_clipboard" />
<check id="native_file_dialog" text="@.native_file_dialog" />
<check id="one_finger_as_mouse_movement"
text="@.one_finger_as_mouse_movement"
tooltip="@.one_finger_as_mouse_movement_tooltip"
pref="experimental.one_finger_as_mouse_movement" />
<hbox id="load_wintab_driver_box">
<check id="load_wintab_driver"
<check id="load_wintab_driver2"
text="@.load_wintab_driver"
tooltip="@.load_wintab_driver_tooltip"
pref="experimental.load_wintab_driver" />
tooltip="@.load_wintab_driver_tooltip" />
<link text="@.wintab_more_info" url="https://www.aseprite.org/docs/wintab/" />
</hbox>
<check id="flash_layer" text="@.flash_selected_layer" />

2
laf

@ -1 +1 @@
Subproject commit 8032d186a751326d0fc6436d69570ad4a3c4aaf1
Subproject commit af0f8e7b53b9e3e689b5fa4e5ce1466c42c9e2aa

View File

@ -138,6 +138,10 @@ void ActiveSiteHandler::onBeforeRemoveLayer(DocEvent& ev)
if (!selectedLayer)
return;
// Remove layer from range
data.range.eraseAndAdjust(ev.layer());
// Select other layer as active
doc::Layer* layerToSelect = candidate_if_layer_is_deleted(selectedLayer, ev.layer());
if (selectedLayer != layerToSelect) {
data.layer = (layerToSelect ? layerToSelect->id():

View File

@ -229,9 +229,14 @@ int App::initialize(const AppOptions& options)
#ifdef _WIN32
if (options.disableWintab() ||
!preferences().experimental.loadWintabDriver()) {
system->useWintabAPI(false);
!preferences().experimental.loadWintabDriver() ||
preferences().tablet.api() == "pointer") {
system->setTabletAPI(os::TabletAPI::WindowsPointerInput);
}
else if (preferences().tablet.api() == "wintab_packets")
system->setTabletAPI(os::TabletAPI::WintabPackets);
else // preferences().tablet.api() == "wintab"
system->setTabletAPI(os::TabletAPI::Wintab);
#endif
system->setAppName(get_app_name());

View File

@ -75,6 +75,7 @@ void FlipCommand::onExecute(Context* ctx)
CelList cels;
if (m_flipMask) {
#ifdef ENABLE_UI
// If we want to flip the visible mask we can go to
// MovingPixelsState (even when the range is enabled, because now
// PixelsMovement support ranges).
@ -88,6 +89,7 @@ void FlipCommand::onExecute(Context* ctx)
return;
}
}
#endif
auto range = site.range();
if (range.enabled()) {
@ -100,8 +102,12 @@ void FlipCommand::onExecute(Context* ctx)
}
if (cels.empty()) {
StatusBar::instance()->showTip(
1000, Strings::statusbar_tips_all_layers_are_locked());
#ifdef ENABLE_UI
if (ctx->isUIAvailable()) {
StatusBar::instance()->showTip(
1000, Strings::statusbar_tips_all_layers_are_locked());
}
#endif // ENABLE_UI
return;
}
}

View File

@ -50,6 +50,7 @@ namespace app {
namespace {
const char* kSectionGeneralId = "section_general";
const char* kSectionTabletId = "section_tablet";
const char* kSectionBgId = "section_bg";
const char* kSectionGridId = "section_grid";
const char* kSectionThemeId = "section_theme";
@ -351,9 +352,47 @@ public:
if (m_pref.experimental.useNativeFileDialog())
nativeFileDialog()->setSelected(true);
#ifndef _WIN32
oneFingerAsMouseMovement()->setVisible(false);
loadWintabDriverBox()->setVisible(false);
#ifdef _WIN32 // Show Tablet section on Windows
{
os::TabletAPI tabletAPI = os::instance()->tabletAPI();
if (tabletAPI == os::TabletAPI::Wintab) {
tabletApiWintabSystem()->setSelected(true);
loadWintabDriver()->setSelected(true);
loadWintabDriver2()->setSelected(true);
}
else if (tabletAPI == os::TabletAPI::WintabPackets) {
tabletApiWintabDirect()->setSelected(true);
loadWintabDriver()->setSelected(true);
loadWintabDriver2()->setSelected(true);
}
else {
tabletApiWindowsPointer()->setSelected(true);
loadWintabDriver()->setSelected(false);
loadWintabDriver2()->setSelected(false);
}
tabletApiWindowsPointer()->Click.connect([this](Event&){ onTabletAPIChange(); });
tabletApiWintabSystem()->Click.connect([this](Event&){ onTabletAPIChange(); });
tabletApiWintabDirect()->Click.connect([this](Event&){ onTabletAPIChange(); });
loadWintabDriver()->Click.connect(
[this](Event&){ onLoadWintabChange(loadWintabDriver()->isSelected()); });
loadWintabDriver2()->Click.connect(
[this](Event&){ onLoadWintabChange(loadWintabDriver2()->isSelected()); });
}
#else // For macOS and Linux
{
// Hide the "section_tablet" item (which is only for Windows at the moment)
for (auto item : sectionListbox()->children()) {
if (static_cast<ListItem*>(item)->getValue() == kSectionTabletId) {
item->setVisible(false);
break;
}
}
sectionTablet()->setVisible(false);
loadWintabDriverBox()->setVisible(false);
loadWintabDriverBox()->setVisible(false);
}
#endif
if (m_pref.experimental.flashLayer())
@ -658,9 +697,35 @@ public:
m_pref.experimental.nonactiveLayersOpacity(nonactiveLayersOpacity()->getValue());
#ifdef _WIN32
manager()->getDisplay()
->setInterpretOneFingerGestureAsMouseMovement(
oneFingerAsMouseMovement()->isSelected());
{
os::TabletAPI tabletAPI = os::TabletAPI::Default;
std::string tabletStr;
bool wintabState = false;
if (tabletApiWindowsPointer()->isSelected()) {
tabletAPI = os::TabletAPI::WindowsPointerInput;
tabletStr = "pointer";
}
else if (tabletApiWintabSystem()->isSelected()) {
tabletAPI = os::TabletAPI::Wintab;
tabletStr = "wintab";
wintabState = true;
}
else if (tabletApiWintabDirect()->isSelected()) {
tabletAPI = os::TabletAPI::WintabPackets;
tabletStr = "wintab_packets";
wintabState = true;
}
m_pref.tablet.api(tabletStr);
m_pref.experimental.loadWintabDriver(wintabState);
manager()->getDisplay()
->setInterpretOneFingerGestureAsMouseMovement(
oneFingerAsMouseMovement()->isSelected());
os::instance()->setTabletAPI(tabletAPI);
}
#endif
ui::set_use_native_cursors(m_pref.cursor.useNativeCursor());
@ -1505,6 +1570,29 @@ private:
}
}
#ifdef _WIN32
void onTabletAPIChange() {
if (tabletApiWindowsPointer()->isSelected()) {
loadWintabDriver()->setSelected(false);
loadWintabDriver2()->setSelected(false);
}
else if (tabletApiWintabSystem()->isSelected() ||
tabletApiWintabDirect()->isSelected()) {
loadWintabDriver()->setSelected(true);
loadWintabDriver2()->setSelected(true);
}
}
void onLoadWintabChange(bool state) {
loadWintabDriver()->setSelected(state);
loadWintabDriver2()->setSelected(state);
if (state)
tabletApiWintabSystem()->setSelected(true);
else
tabletApiWindowsPointer()->setSelected(true);
}
#endif // _WIN32
Context* m_context;
Preferences& m_pref;
DocumentPreferences& m_globPref;

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
@ -11,7 +12,7 @@
#include "app/app.h"
#include "app/commands/command.h"
#include "app/commands/commands.h"
#include "app/commands/params.h"
#include "app/commands/new_params.h"
#include "app/i18n/strings.h"
#include "app/tools/ink_type.h"
#include "app/ui/context_bar.h"
@ -19,60 +20,42 @@
namespace app {
class SetInkTypeCommand : public Command {
struct SetInkTypeParams : public NewParams {
Param<app::tools::InkType> type { this, app::tools::InkType::DEFAULT, "type" };
};
class SetInkTypeCommand : public CommandWithNewParams<SetInkTypeParams> {
public:
SetInkTypeCommand();
protected:
bool onNeedsParams() const override { return true; }
void onLoadParams(const Params& params) override;
bool onChecked(Context* context) override;
void onExecute(Context* context) override;
std::string onGetFriendlyName() const override;
private:
tools::InkType m_type;
};
SetInkTypeCommand::SetInkTypeCommand()
: Command(CommandId::SetInkType(), CmdUIOnlyFlag)
, m_type(tools::InkType::DEFAULT)
: CommandWithNewParams(CommandId::SetInkType(), CmdUIOnlyFlag)
{
}
void SetInkTypeCommand::onLoadParams(const Params& params)
{
std::string typeStr = params.get("type");
if (typeStr == "simple")
m_type = tools::InkType::SIMPLE;
else if (typeStr == "alpha-compositing")
m_type = tools::InkType::ALPHA_COMPOSITING;
else if (typeStr == "copy-color")
m_type = tools::InkType::COPY_COLOR;
else if (typeStr == "lock-alpha")
m_type = tools::InkType::LOCK_ALPHA;
else if (typeStr == "shading")
m_type = tools::InkType::SHADING;
else
m_type = tools::InkType::DEFAULT;
}
bool SetInkTypeCommand::onChecked(Context* context)
{
tools::Tool* tool = App::instance()->activeTool();
return (Preferences::instance().tool(tool).ink() == m_type);
return (Preferences::instance().tool(tool).ink() == params().type());
}
void SetInkTypeCommand::onExecute(Context* context)
{
if (App::instance()->contextBar() != nullptr)
App::instance()->contextBar()->setInkType(m_type);
App::instance()->contextBar()->setInkType(params().type());
}
std::string SetInkTypeCommand::onGetFriendlyName() const
{
std::string ink;
switch (m_type) {
switch (params().type()) {
case tools::InkType::SIMPLE:
ink = Strings::inks_simple_ink();
break;

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -13,6 +13,7 @@
#include "app/color.h"
#include "app/doc_exporter.h"
#include "app/sprite_sheet_type.h"
#include "app/tools/ink_type.h"
#include "base/convert_to.h"
#include "base/split_string.h"
#include "base/string.h"
@ -26,6 +27,7 @@
#ifdef ENABLE_SCRIPTING
#include "app/script/engine.h"
#include "app/script/luacpp.h"
#include "app/script/values.h"
#endif
namespace app {
@ -184,6 +186,12 @@ void Param<filters::ColorCurve>::fromString(const std::string& value)
setValue(curve);
}
template<>
void Param<tools::InkType>::fromString(const std::string& value)
{
setValue(tools::string_id_to_ink_type(value));
}
//////////////////////////////////////////////////////////////////////
// Convert values from Lua
//////////////////////////////////////////////////////////////////////
@ -312,6 +320,12 @@ void Param<filters::ColorCurve>::fromLua(lua_State* L, int index)
}
}
template<>
void Param<tools::InkType>::fromLua(lua_State* L, int index)
{
script::get_value_from_lua<tools::InkType>(L, index);
}
void CommandWithNewParamsBase::loadParamsFromLuaTable(lua_State* L, int index)
{
onResetValues();

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -12,8 +12,8 @@
#include "app/recent_files.h"
#include "app/ini_file.h"
#include "base/convert_to.h"
#include "base/fs.h"
#include "fmt/format.h"
#include <cstdio>
#include <cstring>
@ -225,7 +225,7 @@ void RecentFiles::save()
for (int j=0; j<m_paths[i].size(); ++j) {
set_config_string(section,
base::convert_to<std::string>(j).c_str(),
fmt::format("{:04d}", j).c_str(),
m_paths[i][j].c_str());
}
// Special entry that indicates that we've already converted

View File

@ -36,6 +36,7 @@
#include "app/ui/editor/tool_loop_impl.h"
#include "app/ui/timeline/timeline.h"
#include "app/ui_context.h"
#include "base/clamp.h"
#include "base/fs.h"
#include "base/replace_string.h"
#include "base/version.h"
@ -46,6 +47,7 @@
#include "ui/alert.h"
#include "ver/info.h"
#include <cstring>
#include <iostream>
namespace app {
@ -277,40 +279,113 @@ int App_useTool(lua_State* L)
if (!site.document())
return luaL_error(L, "there is no active document to draw with the tool");
// Options to create the ToolLoop (tool, ink, color, opacity, etc.)
ToolLoopParams params;
// Mouse button
params.button = tools::ToolLoop::Left;
type = lua_getfield(L, 1, "button");
if (type != LUA_TNIL) {
// Only supported button at the moment left (default) or right
if (lua_tointeger(L, -1) == (int)ui::kButtonRight)
params.button = tools::ToolLoop::Right;
}
lua_pop(L, 1);
// Select tool by name
tools::Tool* tool = App::instance()->activeToolManager()->activeTool();
tools::Ink* ink = tool->getInk(0);
const int buttonIdx = (params.button == tools::ToolLoop::Left ? 0: 1);
auto activeToolMgr = App::instance()->activeToolManager();
params.tool = activeToolMgr->activeTool();
params.ink = params.tool->getInk(buttonIdx);
params.controller = params.tool->getController(buttonIdx);
type = lua_getfield(L, 1, "tool");
if (type != LUA_TNIL) {
if (auto toolArg = get_tool_from_arg(L, -1)) {
tool = toolArg;
ink = tool->getInk(0);
params.tool = toolArg;
params.ink = params.tool->getInk(buttonIdx);
params.controller = params.tool->getController(buttonIdx);
}
else
return luaL_error(L, "invalid tool specified in app.useTool() function");
}
lua_pop(L, 1);
// Default color is the active fgColor
app::Color color = Preferences::instance().colorBar.fgColor();
type = lua_getfield(L, 1, "color");
// Select ink by name
type = lua_getfield(L, 1, "ink");
if (type != LUA_TNIL)
color = convert_args_into_color(L, -1);
params.inkType = get_value_from_lua<tools::InkType>(L, -1);
lua_pop(L, 1);
// Default brush is the active brush in the context bar
BrushRef brush(nullptr);
#ifdef ENABLE_UI
if (App::instance()->isGui() &&
App::instance()->contextBar())
brush = App::instance()->contextBar()->activeBrush(tool, ink);
#endif
// Color
type = lua_getfield(L, 1, "color");
if (type != LUA_TNIL)
params.fg = convert_args_into_color(L, -1);
else {
// Default color is the active fgColor
params.fg = Preferences::instance().colorBar.fgColor();
}
lua_pop(L, 1);
type = lua_getfield(L, 1, "bgColor");
if (type != LUA_TNIL)
params.bg = convert_args_into_color(L, -1);
else
params.bg = params.fg;
lua_pop(L, 1);
// Adjust ink depending on "inkType" and "color"
// (e.g. InkType::SIMPLE depends on the color too, to adjust
// eraser/alpha compositing/opaque depending on the color alpha
// value).
params.ink = activeToolMgr->adjustToolInkDependingOnSelectedInkType(
params.ink, params.inkType, params.fg);
// Brush
type = lua_getfield(L, 1, "brush");
if (type != LUA_TNIL)
brush = get_brush_from_arg(L, -1);
params.brush = get_brush_from_arg(L, -1);
else {
// Default brush is the active brush in the context bar
#ifdef ENABLE_UI
if (App::instance()->isGui() &&
App::instance()->contextBar()) {
params.brush = App::instance()
->contextBar()->activeBrush(params.tool,
params.ink);
}
#endif
}
lua_pop(L, 1);
if (!params.brush) {
// In case the brush is nullptr (e.g. there is no UI) we use the
// default 1 pixel brush (e.g. to run scripts from CLI).
params.brush.reset(new Brush(BrushType::kCircleBrushType, 1, 0));
}
// Opacity, tolerance, and others
type = lua_getfield(L, 1, "opacity");
if (type != LUA_TNIL) {
params.opacity = lua_tointeger(L, -1);
params.opacity = base::clamp(params.opacity, 0, 255);
}
lua_pop(L, 1);
type = lua_getfield(L, 1, "tolerance");
if (type != LUA_TNIL) {
params.tolerance = lua_tointeger(L, -1);
params.tolerance = base::clamp(params.tolerance, 0, 255);
}
lua_pop(L, 1);
type = lua_getfield(L, 1, "contiguous");
if (type != LUA_TNIL)
params.contiguous = lua_toboolean(L, -1);
lua_pop(L, 1);
type = lua_getfield(L, 1, "freehandAlgorithm");
if (type != LUA_TNIL)
params.freehandAlgorithm = get_value_from_lua<tools::FreehandAlgorithm>(L, -1);
lua_pop(L, 1);
if (!brush)
brush.reset(new Brush(BrushType::kCircleBrushType, 1, 0));
// How the tileset must be modified depending on this tool usage
type = lua_getfield(L, 1, "tilesetMode");
@ -323,7 +398,7 @@ int App_useTool(lua_State* L)
type = lua_getfield(L, 1, "points");
if (type == LUA_TTABLE) {
std::unique_ptr<tools::ToolLoop> loop(
create_tool_loop_for_script(ctx, site, tool, ink, color, brush));
create_tool_loop_for_script(ctx, site, params));
if (!loop)
return luaL_error(L, "cannot draw in the active site");

View File

@ -19,6 +19,7 @@
#include "app/script/security.h"
#include "app/sprite_sheet_type.h"
#include "app/tileset_mode.h"
#include "app/tools/ink_type.h"
#include "base/chrono.h"
#include "base/file_handle.h"
#include "base/fs.h"
@ -334,6 +335,16 @@ Engine::Engine()
setfield_integer(L, "NONE", doc::BrushPattern::PAINT_BRUSH);
lua_pop(L, 1);
lua_newtable(L);
lua_pushvalue(L, -1);
lua_setglobal(L, "Ink");
setfield_integer(L, "SIMPLE", app::tools::InkType::SIMPLE);
setfield_integer(L, "ALPHA_COMPOSITING", app::tools::InkType::ALPHA_COMPOSITING);
setfield_integer(L, "COPY_COLOR", app::tools::InkType::COPY_COLOR);
setfield_integer(L, "LOCK_ALPHA", app::tools::InkType::LOCK_ALPHA);
setfield_integer(L, "SHADING", app::tools::InkType::SHADING);
lua_pop(L, 1);
lua_newtable(L);
lua_pushvalue(L, -1);
lua_setglobal(L, "FilterChannels");

View File

@ -50,14 +50,14 @@ namespace doc {
class WithUserData;
}
namespace tools {
class Tool;
}
namespace app {
class Site;
namespace tools {
class Tool;
}
namespace script {
enum class FileAccessMode {
@ -151,7 +151,7 @@ namespace app {
void push_tileset(lua_State* L, doc::Tileset* tileset);
void push_tileset_image(lua_State* L, doc::Tileset* tileset, doc::Image* image);
void push_tilesets(lua_State* L, doc::Tilesets* tilesets);
void push_tool(lua_State* L, tools::Tool* tool);
void push_tool(lua_State* L, app::tools::Tool* tool);
void push_userdata(lua_State* L, doc::WithUserData* userData);
void push_version(lua_State* L, const base::Version& ver);
@ -167,7 +167,7 @@ namespace app {
doc::Cel* get_image_cel_from_arg(lua_State* L, int index);
doc::frame_t get_frame_number_from_arg(lua_State* L, int index);
const doc::Mask* get_mask_from_arg(lua_State* L, int index);
tools::Tool* get_tool_from_arg(lua_State* L, int index);
app::tools::Tool* get_tool_from_arg(lua_State* L, int index);
doc::BrushRef get_brush_from_arg(lua_State* L, int index);
// Used by App.open(), Sprite{ fromFile }, and Image{ fromFile }

View File

@ -345,9 +345,13 @@ int Layer_set_parent(lua_State* L)
else if (auto parentLayer = may_get_docobj<Layer>(L, 2)) {
if (parentLayer->isGroup())
parent = static_cast<LayerGroup*>(parentLayer);
else
return luaL_error(L, "the given parent is not a layer group or sprite");
}
if (parent == layer)
if (!parent)
return luaL_error(L, "parent cannot be nil");
else if (parent == layer)
return luaL_error(L, "the parent of a layer cannot be the layer itself");
// TODO Why? should we be able to do this? It would require some hard work:

View File

@ -126,6 +126,23 @@ gfx::Rect get_value_from_lua(lua_State* L, int index) {
return convert_args_into_rect(L, index);
}
// ----------------------------------------------------------------------
// tools::InkType
template<>
void push_value_to_lua(lua_State* L, const app::tools::InkType& inkType) {
lua_pushinteger(L, (int)inkType);
}
template<>
app::tools::InkType get_value_from_lua(lua_State* L, int index) {
if (lua_type(L, index) == LUA_TSTRING) {
if (const char* s = lua_tostring(L, index))
return app::tools::string_id_to_ink_type(s);
}
return (app::tools::InkType)lua_tointeger(L, index);
}
// ----------------------------------------------------------------------
// enums
@ -163,7 +180,6 @@ FOR_ENUM(app::gen::TimelinePosition)
FOR_ENUM(app::gen::WindowColorProfile)
FOR_ENUM(app::gen::ToGrayAlgorithm)
FOR_ENUM(app::tools::FreehandAlgorithm)
FOR_ENUM(app::tools::InkType)
FOR_ENUM(app::tools::RotationAlgorithm)
FOR_ENUM(doc::AniDir)
FOR_ENUM(doc::BrushPattern)

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2016 David Capello
//
// This program is distributed under the terms of
@ -11,6 +11,7 @@
#include "app/tools/active_tool.h"
#include "app/color.h"
#include "app/pref/preferences.h"
#include "app/tools/active_tool_observer.h"
#include "app/tools/ink.h"
@ -76,26 +77,34 @@ Ink* ActiveToolManager::activeInk() const
Tool* tool = activeTool();
Ink* ink = tool->getInk(m_rightClick ? 1: 0);
if (ink->isPaint() && !ink->isEffect()) {
tools::InkType inkType = Preferences::instance().tool(tool).ink();
const char* id = nullptr;
switch (inkType) {
case tools::InkType::SIMPLE: {
id = tools::WellKnownInks::Paint;
const tools::InkType inkType = Preferences::instance().tool(tool).ink();
app::Color color;
#ifdef ENABLE_UI
ColorBar* colorbar = ColorBar::instance();
app::Color color = (m_rightClick ? colorbar->getBgColor():
colorbar->getFgColor());
ColorBar* colorbar = ColorBar::instance();
color = (m_rightClick ? colorbar->getBgColor():
colorbar->getFgColor());
#endif
ink = adjustToolInkDependingOnSelectedInkType(ink, inkType, color);
}
return ink;
}
Ink* ActiveToolManager::adjustToolInkDependingOnSelectedInkType(
Ink* ink,
const InkType inkType,
const app::Color& color) const
{
if (ink->isPaint() && !ink->isEffect()) {
const char* id = nullptr;
switch (inkType) {
case tools::InkType::SIMPLE:
id = tools::WellKnownInks::Paint;
if (color.getAlpha() == 0)
id = tools::WellKnownInks::PaintCopy;
#endif
break;
}
case tools::InkType::ALPHA_COMPOSITING:
id = tools::WellKnownInks::Paint;
id = tools::WellKnownInks::PaintAlphaCompositing;
break;
case tools::InkType::COPY_COLOR:
id = tools::WellKnownInks::PaintCopy;
@ -107,11 +116,9 @@ Ink* ActiveToolManager::activeInk() const
id = tools::WellKnownInks::Shading;
break;
}
if (id)
ink = m_toolbox->getInkById(id);
}
return ink;
}

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2016 David Capello
//
// This program is distributed under the terms of
@ -8,9 +9,12 @@
#define APP_TOOLS_ACTIVE_TOOL_H_INCLUDED
#pragma once
#include "app/tools/ink_type.h"
#include "obs/observable.h"
namespace app {
class Color;
namespace tools {
class ActiveToolObserver;
@ -44,6 +48,11 @@ public:
void releaseButtons();
void setSelectedTool(Tool* tool);
Ink* adjustToolInkDependingOnSelectedInkType(
Ink* ink,
const InkType inkType,
const app::Color& color) const;
private:
static bool isToolAffectedByRightClickMode(Tool* tool);

View File

@ -273,7 +273,8 @@ public:
m_palette(get_current_palette()),
m_rgbmap(loop->getRgbMap()),
m_opacity(loop->getOpacity()),
m_maskIndex(loop->getLayer()->isBackground() ? -1: loop->sprite()->transparentColor()) {
m_maskIndex(loop->getLayer()->isBackground() ? -1: loop->sprite()->transparentColor()),
m_colorIndex(loop->getFgColor()) {
}
void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y) override {
@ -281,6 +282,9 @@ public:
}
void processPixel(int x, int y) {
if (m_colorIndex == m_maskIndex)
return;
color_t c = *m_srcAddress;
if (int(c) == m_maskIndex)
c = m_palette->getEntry(c) & rgba_rgb_mask; // Alpha = 0
@ -300,6 +304,7 @@ private:
const int m_opacity;
color_t m_color;
const int m_maskIndex;
int m_colorIndex;
};
//////////////////////////////////////////////////////////////////////
@ -1119,6 +1124,14 @@ private:
// Brush Ink - Base
//////////////////////////////////////////////////////////////////////
// TODO In all cases where we get the brush index and use that index
// in m_palette->getEntry(index), the color is converted to the
// sprite palette or to the grayscale palette (for grayscale
// sprites), we might want to save the original palette in the
// brush to use that one in these cases (not sure if this does
// applies when we select a new foreground/background color in
// the color bar and the brush color changes, or if this should
// be a new optional flag/parameter to save on each brush)
template<typename ImageTraits>
class BrushInkProcessingBase : public DoubleInkProcessing<BrushInkProcessingBase<ImageTraits>, ImageTraits> {
public:
@ -1136,7 +1149,15 @@ public:
m_height = m_brush->bounds().h;
m_u = (m_brush->patternOrigin().x - loop->getCelOrigin().x) % m_width;
m_v = (m_brush->patternOrigin().y - loop->getCelOrigin().y) % m_height;
m_transparentColor = loop->sprite()->transparentColor();
if (loop->sprite()->colorMode() == ColorMode::INDEXED) {
if (loop->getLayer()->isTransparent())
m_transparentColor = loop->sprite()->transparentColor();
else
m_transparentColor = -1;
}
else
m_transparentColor = 0;
}
void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y) override {
@ -1259,10 +1280,6 @@ bool BrushInkProcessingBase<RgbTraits>::preProcessPixel(int x, int y, color_t* r
break;
}
case IMAGE_INDEXED: {
// TODO m_palette->getEntry(c) does not work because the m_palette member is
// loaded the Graya Palette, NOT the original Indexed Palette from where m_brushImage belongs.
// This conversion can be possible if we load the palette pointer in m_brush when
// is created the custom brush in the Indexed Sprite.
c = get_pixel_fast<IndexedTraits>(m_brushImage, x, y);
if (m_transparentColor == c)
c = 0;
@ -1305,12 +1322,6 @@ bool BrushInkProcessingBase<GrayscaleTraits>::preProcessPixel(int x, int y, colo
break;
}
case IMAGE_INDEXED: {
// TODO m_palette->getEntry(c) does not work because the
// m_palette member is loaded the Graya Palette, NOT the
// original Indexed Palette from where m_brushImage belongs.
// This conversion can be possible if we load the palette
// pointer in m_brush when is created the custom brush in the
// Indexed Sprite.
c = get_pixel_fast<IndexedTraits>(m_brushImage, x, y);
if (m_transparentColor == c)
c = 0;
@ -1348,19 +1359,43 @@ bool BrushInkProcessingBase<IndexedTraits>::preProcessPixel(int x, int y, color_
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), rgba_geta(c), 0);
color_t d = m_palette->getEntry(*m_dstAddress);
c = rgba_blender_normal(d, c, m_opacity);
c = m_palette->findBestfit(rgba_getr(c),
rgba_getg(c),
rgba_getb(c),
rgba_geta(c), m_transparentColor);
break;
}
case IMAGE_INDEXED: {
c = get_pixel_fast<IndexedTraits>(m_brushImage, x, y);
if (c == m_transparentColor)
return false;
color_t f = m_palette->getEntry(c);
// Keep original index in special opaque case
if (rgba_geta(f) == 255 && m_opacity == 255)
break;
color_t b = m_palette->getEntry(*m_dstAddress);
c = rgba_blender_normal(b, f, m_opacity);
c = m_palette->findBestfit(rgba_getr(c),
rgba_getg(c),
rgba_getb(c),
rgba_geta(c), m_transparentColor);
break;
}
case IMAGE_GRAYSCALE: {
c = get_pixel_fast<GrayscaleTraits>(m_brushImage, x, y);
color_t b = m_palette->getEntry(*m_dstAddress);
b = graya(rgba_luma(b),
rgba_geta(b));
c = graya_blender_normal(b, c, m_opacity);
c = m_palette->findBestfit(graya_getv(c),
graya_getv(c),
graya_getv(c),
graya_geta(c), 0);
graya_geta(c), m_transparentColor);
break;
}
case IMAGE_BITMAP: {
@ -1490,12 +1525,6 @@ void BrushEraserInkProcessing<RgbTraits>::processPixel(int x, int y) {
if (m_transparentColor == c)
c = 0;
else
// TODO m_palette->getEntry(c) does not work because the
// m_palette member is loaded the Rgba Palette, NOT the
// original Indexed Palette from where m_brushImage belongs.
// This conversion can be possible if we load the palette
// pointer in m_brush when is created the custom brush in the
// Indexed Sprite.
c = m_palette->getEntry(c);
int t;
c = doc::rgba(rgba_getr(*m_srcAddress),
@ -1544,14 +1573,9 @@ void BrushEraserInkProcessing<GrayscaleTraits>::processPixel(int x, int y) {
c = get_pixel_fast<IndexedTraits>(m_brushImage, x, y);
if (m_transparentColor == c)
c = 0;
else
// TODO m_palette->getEntry(c) does not work because the
// m_palette member is loaded the Graya Palette, NOT the
// original Indexed Palette from where m_brushImage belongs.
// This conversion can be possible if we load the palette
// pointer in m_brush when is created the custom brush in the
// Indexed Sprite.
else {
c = m_palette->getEntry(c);
}
int t;
c = graya(graya_getv(*m_srcAddress),
MUL_UN8(graya_geta(*m_dstAddress), 255 - rgba_geta(c), t));
@ -1601,7 +1625,7 @@ void BrushEraserInkProcessing<IndexedTraits>::processPixel(int x, int y) {
c = m_palette->findBestfit(graya_getv(c),
graya_getv(c),
graya_getv(c),
graya_geta(c), 0);
graya_geta(c), m_transparentColor);
break;
}
case IMAGE_BITMAP: {
@ -1726,10 +1750,6 @@ void BrushShadingInkProcessing<GrayscaleTraits>::processPixel(int x, int y) {
break;
}
case IMAGE_INDEXED: {
// TODO m_palette->getEntry(c) does not work because the m_palette member is
// loaded the Graya Palette, NOT the original Indexed Palette from where m_brushImage belongs.
// This conversion can be possible if we load the palette pointer in m_brush when
// is created the custom brush in the Indexed Sprite.
auto c = get_pixel_fast<IndexedTraits>(m_brushImage, x, y);
if (m_transparentColor != c)
*m_dstAddress = m_shading(*m_srcAddress);
@ -1776,10 +1796,6 @@ void BrushCopyInkProcessing<RgbTraits>::processPixel(int x, int y) {
break;
}
case IMAGE_INDEXED: {
// TODO m_palette->getEntry(c) does not work because the m_palette member is
// loaded the Graya Palette, NOT the original Indexed Palette from where m_brushImage belongs.
// This conversion can be possible if we load the palette pointer in m_brush when
// is created the custom brush in the Indexed Sprite.
c = get_pixel_fast<IndexedTraits>(m_brushImage, x, y);
if (c == m_transparentColor) {
*m_dstAddress = *m_srcAddress;
@ -1819,7 +1835,10 @@ void BrushCopyInkProcessing<IndexedTraits>::processPixel(int x, int y) {
c = get_pixel_fast<RgbTraits>(m_brushImage, x, y);
if (rgba_geta(c) == 0)
return;
c = m_palette->findBestfit(rgba_getr(c), rgba_getg(c), rgba_getb(c), rgba_geta(c), 0);
c = m_palette->findBestfit(rgba_getr(c),
rgba_getg(c),
rgba_getb(c),
rgba_geta(c), m_transparentColor);
if (c == 0)
c = *m_srcAddress;
break;
@ -1837,7 +1856,7 @@ void BrushCopyInkProcessing<IndexedTraits>::processPixel(int x, int y) {
c = m_palette->findBestfit(graya_getv(c),
graya_getv(c),
graya_getv(c),
graya_geta(c), 0);
graya_geta(c), m_transparentColor);
break;
}
case IMAGE_BITMAP: {
@ -1868,12 +1887,6 @@ void BrushCopyInkProcessing<GrayscaleTraits>::processPixel(int x, int y) {
break;
}
case IMAGE_INDEXED: {
// TODO m_palette->getEntry(c) does not work because the
// m_palette member is loaded the Graya Palette, NOT the
// original Indexed Palette from where m_brushImage belongs.
// This conversion can be possible if we load the palette
// pointer in m_brush when is created the custom brush in the
// Indexed Sprite.
c = get_pixel_fast<IndexedTraits>(m_brushImage, x, y);
if (c == m_transparentColor) {
*m_dstAddress = *m_srcAddress;

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2001-2015 David Capello
//
// This program is distributed under the terms of
@ -39,11 +40,24 @@ std::string ink_type_to_string_id(InkType inkType)
InkType string_id_to_ink_type(const std::string& s)
{
if (s == "simple") return tools::InkType::SIMPLE;
if (s == "alpha_compositing") return tools::InkType::ALPHA_COMPOSITING;
if (s == "copy_color") return tools::InkType::COPY_COLOR;
if (s == "lock_alpha") return tools::InkType::LOCK_ALPHA;
if (s == "shading") return tools::InkType::SHADING;
if (s == "simple")
return tools::InkType::SIMPLE;
if (s == "alpha_compositing" ||
s == "alpha-compositing")
return tools::InkType::ALPHA_COMPOSITING;
if (s == "copy_color" ||
s == "copy-color")
return tools::InkType::COPY_COLOR;
if (s == "lock_alpha" ||
s == "lock-alpha")
return tools::InkType::LOCK_ALPHA;
if (s == "shading")
return tools::InkType::SHADING;
return tools::InkType::DEFAULT;
}

View File

@ -66,7 +66,7 @@ private:
// (or foreground/background colors)
class PaintInk : public BaseInk {
public:
enum Type { Simple, WithFg, WithBg, Copy, LockAlpha };
enum Type { Simple, WithFg, WithBg, AlphaCompositing, Copy, LockAlpha};
private:
Type m_type;
@ -114,9 +114,11 @@ public:
}
else {
switch (m_type) {
case Simple: {
case Simple:
case AlphaCompositing: {
bool opaque = false;
// Opacity is set to 255 when InkType=Simple in ToolLoopBase()
if (loop->getOpacity() == 255 &&
// The trace policy is "overlap" when the dynamics has
// a gradient between FG <-> BG
@ -134,8 +136,16 @@ public:
opaque = (graya_geta(color) == 255);
break;
case IMAGE_INDEXED:
color = get_current_palette()->getEntry(color);
opaque = (rgba_geta(color) == 255);
// Simple ink for indexed is better to use always
// opaque if opacity == 255.
if (m_type == Simple)
opaque = true;
else if (color == loop->sprite()->transparentColor())
opaque = false;
else {
color = get_current_palette()->getEntry(color);
opaque = (rgba_geta(color) == 255);
}
break;
}
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -56,6 +56,7 @@ const char* WellKnownInks::Selection = "selection";
const char* WellKnownInks::Paint = "paint";
const char* WellKnownInks::PaintFg = "paint_fg";
const char* WellKnownInks::PaintBg = "paint_bg";
const char* WellKnownInks::PaintAlphaCompositing = "paint_alpha_compositing";
const char* WellKnownInks::PaintCopy = "paint_copy";
const char* WellKnownInks::PaintLockAlpha = "paint_lock_alpha";
const char* WellKnownInks::Shading = "shading";
@ -115,6 +116,7 @@ ToolBox::ToolBox()
m_inks[WellKnownInks::Paint] = new PaintInk(PaintInk::Simple);
m_inks[WellKnownInks::PaintFg] = new PaintInk(PaintInk::WithFg);
m_inks[WellKnownInks::PaintBg] = new PaintInk(PaintInk::WithBg);
m_inks[WellKnownInks::PaintAlphaCompositing] = new PaintInk(PaintInk::AlphaCompositing);
m_inks[WellKnownInks::PaintCopy] = new PaintInk(PaintInk::Copy);
m_inks[WellKnownInks::PaintLockAlpha] = new PaintInk(PaintInk::LockAlpha);
m_inks[WellKnownInks::Gradient] = new GradientInk();

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -36,6 +36,7 @@ namespace app {
extern const char* Paint;
extern const char* PaintFg;
extern const char* PaintBg;
extern const char* PaintAlphaCompositing;
extern const char* PaintCopy;
extern const char* PaintLockAlpha;
extern const char* Shading;

View File

@ -976,10 +976,13 @@ void Editor::drawMask(Graphics* g)
os::Paint paint;
paint.style(os::Paint::Stroke);
paint.color(gfx::rgba(0, 0, 0));
g->setMatrix(Matrix::MakeTrans(pt.x, pt.y));
g->concat(m_proj.scaleMatrix());
g->drawPath(segs.path(), paint);
g->resetMatrix();
// We translate the path instead of applying a matrix to the
// ui::Graphics so the "checked" pattern is not scaled too.
gfx::Path path;
segs.path().transform(m_proj.scaleMatrix(), &path);
path.offset(pt.x, pt.y);
g->drawPath(path, paint);
}
void Editor::drawMaskSafe()

View File

@ -63,6 +63,22 @@ namespace app {
using namespace ui;
#ifdef ENABLE_UI
static void fill_toolloop_params_from_tool_preferences(ToolLoopParams& params)
{
ToolPreferences& toolPref =
Preferences::instance().tool(params.tool);
params.inkType = toolPref.ink();
params.opacity = toolPref.opacity();
params.tolerance = toolPref.tolerance();
params.contiguous = toolPref.contiguous();
params.freehandAlgorithm = toolPref.freehandAlgorithm();
}
#endif // ENABLE_UI
//////////////////////////////////////////////////////////////////////
// Common properties between drawing/preview ToolLoop impl
@ -107,17 +123,11 @@ protected:
public:
ToolLoopBase(Editor* editor, Site site,
const gfx::Rect& gridBounds,
tools::Tool* tool, tools::Ink* ink,
tools::Controller* controller,
const BrushRef& brush,
tools::ToolLoop::Button button,
const app::Color& fgColor,
const app::Color& bgColor)
ToolLoopParams& params)
: m_editor(editor)
, m_tool(tool)
, m_brush(brush)
, m_origBrush(brush)
, m_tool(params.tool)
, m_brush(params.brush)
, m_origBrush(params.brush)
, m_oldPatternOrigin(m_brush->patternOrigin())
, m_document(site.document())
, m_sprite(site.sprite())
@ -126,15 +136,15 @@ public:
, m_rgbMap(nullptr)
, m_docPref(Preferences::instance().document(m_document))
, m_toolPref(Preferences::instance().tool(m_tool))
, m_opacity(m_toolPref.opacity())
, m_tolerance(m_toolPref.tolerance())
, m_contiguous(m_toolPref.contiguous())
, m_opacity(params.opacity)
, m_tolerance(params.tolerance)
, m_contiguous(params.contiguous)
, m_snapToGrid(m_docPref.grid.snap())
, m_isSelectingTiles(false)
, m_gridBounds(gridBounds)
, m_button(button)
, m_ink(ink->clone())
, m_controller(controller)
, m_gridBounds(params.gridBounds)
, m_button(params.button)
, m_ink(params.ink->clone())
, m_controller(params.controller)
, m_pointShape(m_tool->getPointShape(m_button))
, m_intertwine(m_tool->getIntertwine(m_button))
, m_tracePolicy(m_tool->getTracePolicy(m_button))
@ -143,11 +153,15 @@ public:
ColorTarget(ColorTarget::BackgroundLayer,
m_sprite->pixelFormat(),
m_sprite->transparentColor()))
, m_fgColor(color_utils::color_for_target_mask(fgColor, m_colorTarget))
, m_bgColor(color_utils::color_for_target_mask(bgColor, m_colorTarget))
, m_primaryColor(button == tools::ToolLoop::Left ? m_fgColor: m_bgColor)
, m_secondaryColor(button == tools::ToolLoop::Left ? m_bgColor: m_fgColor)
, m_fgColor(color_utils::color_for_target_mask(params.fg, m_colorTarget))
, m_bgColor(color_utils::color_for_target_mask(params.bg, m_colorTarget))
, m_primaryColor(m_button == tools::ToolLoop::Left ? m_fgColor: m_bgColor)
, m_secondaryColor(m_button == tools::ToolLoop::Left ? m_bgColor: m_fgColor)
{
ASSERT(m_tool);
ASSERT(m_ink);
ASSERT(m_controller);
#ifdef ENABLE_UI // TODO add dynamics support when UI is not enabled
if (m_controller->isFreehand() &&
!m_pointShape->isFloodFill() &&
@ -160,7 +174,7 @@ public:
m_tracePolicy == tools::TracePolicy::AccumulateUpdateLast) {
tools::ToolBox* toolbox = App::instance()->toolBox();
switch (m_toolPref.freehandAlgorithm()) {
switch (params.freehandAlgorithm) {
case tools::FreehandAlgorithm::DEFAULT:
m_intertwine = toolbox->getIntertwinerById(tools::WellKnownIntertwiners::AsLines);
m_tracePolicy = tools::TracePolicy::Accumulate;
@ -214,18 +228,18 @@ public:
}
// Ignore opacity for these inks
if (!tools::inkHasOpacity(m_toolPref.ink()) &&
if (!tools::inkHasOpacity(params.inkType) &&
m_brush->type() != kImageBrushType &&
!m_ink->isEffect()) {
m_opacity = 255;
}
#ifdef ENABLE_UI // TODO add support when UI is not enabled
if (m_toolPref.ink() == tools::InkType::SHADING) {
if (params.inkType == tools::InkType::SHADING) {
m_shade = App::instance()->contextBar()->getShade();
m_shadingRemap.reset(
App::instance()->contextBar()->createShadeRemap(
button == tools::ToolLoop::Left));
m_button == tools::ToolLoop::Left));
}
#endif
}
@ -418,19 +432,10 @@ class ToolLoopImpl : public ToolLoopBase {
public:
ToolLoopImpl(Editor* editor,
Site site,
const gfx::Rect& gridBounds,
Context* context,
tools::Tool* tool,
tools::Ink* ink,
tools::Controller* controller,
const BrushRef& brush,
tools::ToolLoop::Button button,
const app::Color& fgColor,
const app::Color& bgColor,
ToolLoopParams& params,
const bool saveLastPoint)
: ToolLoopBase(editor, site, gridBounds,
tool, ink, controller, brush,
button, fgColor, bgColor)
: ToolLoopBase(editor, site, params)
, m_context(context)
, m_canceled(false)
, m_tx(m_context,
@ -487,7 +492,7 @@ public:
m_floodfillSrcImage = const_cast<Image*>(getSrcImage());
// Settings
switch (tool->getFill(m_button)) {
switch (m_tool->getFill(m_button)) {
case tools::FillNone:
m_filled = false;
break;
@ -673,6 +678,9 @@ private:
};
//////////////////////////////////////////////////////////////////////
// For user UI painting
#ifdef ENABLE_UI
tools::ToolLoop* create_tool_loop(
@ -682,14 +690,15 @@ tools::ToolLoop* create_tool_loop(
const bool convertLineToFreehand,
const bool selectTiles)
{
tools::Tool* tool = editor->getCurrentEditorTool();
tools::Ink* ink = editor->getCurrentEditorInk();
if (!tool || !ink)
ToolLoopParams params;
params.tool = editor->getCurrentEditorTool();
params.ink = editor->getCurrentEditorInk();
if (!params.tool || !params.ink)
return nullptr;
if (selectTiles) {
tool = App::instance()->toolBox()->getToolById(tools::WellKnownTools::RectangularMarquee);
ink = tool->getInk(button == tools::Pointer::Left ? 0: 1);
params.tool = App::instance()->toolBox()->getToolById(tools::WellKnownTools::RectangularMarquee);
params.ink = params.tool->getInk(button == tools::Pointer::Left ? 0: 1);
}
Site site = editor->getSite();
@ -698,7 +707,7 @@ tools::ToolLoop* create_tool_loop(
// site.layer(nullptr) in certain cases, we need to know if the
// active layer is a tilemap, and in that case the grid bounds can
// be different than the sprite grid bounds).
gfx::Rect gridBounds = site.gridBounds();
params.gridBounds = site.gridBounds();
// For selection tools, we can use any layer (even without layers at
// all), so we specify a nullptr here as the active layer. This is
@ -709,8 +718,9 @@ tools::ToolLoop* create_tool_loop(
// Anyway this cannot be used in 'magic wand' tool (isSelection +
// isFloodFill) because we need the original layer source
// image/pixels to stop the flood-fill algorithm.
if (ink->isSelection() &&
!tool->getPointShape(button != tools::Pointer::Left ? 1: 0)->isFloodFill()) {
if (params.ink->isSelection() &&
!params.tool->getPointShape(
button != tools::Pointer::Left ? 1: 0)->isFloodFill()) {
site.layer(nullptr);
}
else {
@ -741,10 +751,11 @@ tools::ToolLoop* create_tool_loop(
// Get fg/bg colors
ColorBar* colorbar = ColorBar::instance();
app::Color fg = colorbar->getFgColor();
app::Color bg = colorbar->getBgColor();
params.fg = colorbar->getFgColor();
params.bg = colorbar->getBgColor();
if (!fg.isValid() || !bg.isValid()) {
if (!params.fg.isValid() ||
!params.bg.isValid()) {
if (Preferences::instance().colorBar.showInvalidFgBgColorAlert()) {
OptionalAlert::show(
Preferences::instance().colorBar.showInvalidFgBgColorAlert,
@ -755,28 +766,29 @@ tools::ToolLoop* create_tool_loop(
// Create the new tool loop
try {
tools::ToolLoop::Button toolLoopButton =
params.button =
(button == tools::Pointer::Left ? tools::ToolLoop::Left:
tools::ToolLoop::Right);
tools::Controller* controller =
params.controller =
(convertLineToFreehand ?
App::instance()->toolBox()->getControllerById(
tools::WellKnownControllers::LineFreehand):
tool->getController(toolLoopButton));
params.tool->getController(params.button));
const bool saveLastPoint =
(ink->isPaint() &&
(controller->isFreehand() ||
(params.ink->isPaint() &&
(params.controller->isFreehand() ||
convertLineToFreehand));
params.brush = App::instance()->contextBar()
->activeBrush(params.tool, params.ink);
fill_toolloop_params_from_tool_preferences(params);
ASSERT(context->activeDocument() == editor->document());
auto toolLoop = new ToolLoopImpl(
editor, site, gridBounds, context,
tool, ink, controller,
App::instance()->contextBar()->activeBrush(tool, ink),
toolLoopButton, fg, bg,
saveLastPoint);
editor, site, context, params, saveLastPoint);
if (selectTiles)
toolLoop->forceSnapToTiles();
@ -791,33 +803,33 @@ tools::ToolLoop* create_tool_loop(
#endif // ENABLE_UI
//////////////////////////////////////////////////////////////////////
// For scripting
#ifdef ENABLE_SCRIPTING
tools::ToolLoop* create_tool_loop_for_script(
Context* context,
const Site& site,
tools::Tool* tool,
tools::Ink* ink,
const app::Color& color,
const doc::BrushRef& brush)
ToolLoopParams& params)
{
ASSERT(tool);
ASSERT(ink);
ASSERT(params.tool);
ASSERT(params.ink);
if (!site.layer())
return nullptr;
try {
const tools::ToolLoop::Button toolLoopButton = tools::ToolLoop::Left;
tools::Controller* controller = tool->getController(toolLoopButton);
// TODO should gridBounds be specified by the caller?
params.gridBounds = site.gridBounds();
try {
// If we don't have the UI available, we reset the tools
// preferences, so scripts that are executed in batch mode have a
// reproducible behavior.
if (!context->isUIAvailable())
Preferences::instance().resetToolPreferences(tool);
Preferences::instance().resetToolPreferences(params.tool);
return new ToolLoopImpl(
nullptr, site, site.gridBounds(), context,
tool, ink, controller, brush,
toolLoopButton, color, color, false);
nullptr, site, context, params, false);
}
catch (const std::exception& ex) {
Console::showException(ex);
@ -825,8 +837,10 @@ tools::ToolLoop* create_tool_loop_for_script(
}
}
#endif // ENABLE_SCRIPTING
//////////////////////////////////////////////////////////////////////
// For preview
// For UI preview
#ifdef ENABLE_UI
@ -836,17 +850,10 @@ class PreviewToolLoopImpl : public ToolLoopBase {
public:
PreviewToolLoopImpl(
Editor* editor,
tools::Tool* tool,
tools::Ink* ink,
const BrushRef& brush,
const app::Color& fgColor,
const app::Color& bgColor,
ToolLoopParams& params,
Image* image,
const gfx::Point& celOrigin)
: ToolLoopBase(editor, editor->getSite(),
editor->getSite().gridBounds(),
tool, ink, tool->getController(tools::ToolLoop::Left),
brush, tools::ToolLoop::Left, fgColor, bgColor)
: ToolLoopBase(editor, editor->getSite(), params)
, m_image(image)
{
m_celOrigin = celOrigin;
@ -900,10 +907,11 @@ tools::ToolLoop* create_tool_loop_preview(
Image* image,
const gfx::Point& celOrigin)
{
tools::Tool* tool = editor->getCurrentEditorTool();
tools::Ink* ink = editor->getCurrentEditorInk();
if (!tool || !ink)
return NULL;
ToolLoopParams params;
params.tool = editor->getCurrentEditorTool();
params.ink = editor->getCurrentEditorInk();
if (!params.tool || !params.ink)
return nullptr;
Layer* layer = editor->layer();
if (!layer ||
@ -915,18 +923,26 @@ tools::ToolLoop* create_tool_loop_preview(
// Get fg/bg colors
ColorBar* colorbar = ColorBar::instance();
app::Color fg = colorbar->getFgColor();
app::Color bg = colorbar->getBgColor();
if (!fg.isValid() || !bg.isValid())
params.fg = colorbar->getFgColor();
params.bg = colorbar->getBgColor();
if (!params.fg.isValid() ||
!params.bg.isValid())
return nullptr;
params.brush = brush;
params.button = tools::ToolLoop::Left;
params.controller = params.tool->getController(params.button);
params.gridBounds = editor->getSite().gridBounds();
// Create the new tool loop
try {
fill_toolloop_params_from_tool_preferences(params);
return new PreviewToolLoopImpl(
editor, tool, ink, brush,
fg, bg, image, celOrigin);
editor, params, image, celOrigin);
}
catch (const std::exception&) {
catch (const std::exception& e) {
LOG(ERROR, e.what());
return nullptr;
}
}

View File

@ -9,7 +9,11 @@
#define APP_UI_EDITOR_TOOL_LOOP_IMPL_H_INCLUDED
#pragma once
#include "app/color.h"
#include "app/tools/freehand_algorithm.h"
#include "app/tools/ink_type.h"
#include "app/tools/pointer.h"
#include "app/tools/tool_loop.h"
#include "doc/brush.h"
#include "doc/image_ref.h"
#include "gfx/fwd.h"
@ -24,11 +28,32 @@ namespace app {
class Editor;
class Site;
namespace tools {
class Ink;
class Tool;
class ToolLoop;
}
struct ToolLoopParams {
tools::Tool* tool = nullptr;
tools::Ink* ink = nullptr;
tools::Controller* controller = nullptr;
tools::ToolLoop::Button button = tools::ToolLoop::Left;
tools::InkType inkType = tools::InkType::DEFAULT;
app::Color fg;
app::Color bg;
doc::BrushRef brush;
// Options equal to (and with the same default as in)
// <preferences><tool>...</tool></preferences> from
// "data/pref.xml" file.
int opacity = 255;
int tolerance = 0;
bool contiguous = true;
tools::FreehandAlgorithm freehandAlgorithm = tools::FreehandAlgorithm::DEFAULT;
gfx::Rect gridBounds;
};
//////////////////////////////////////////////////////////////////////
// For UI
#ifdef ENABLE_UI
tools::ToolLoop* create_tool_loop(
Editor* editor,
@ -37,20 +62,26 @@ namespace app {
const bool convertLineToFreehand,
const bool selectTiles);
tools::ToolLoop* create_tool_loop_for_script(
Context* context,
const Site& site,
tools::Tool* tool,
tools::Ink* ink,
const app::Color& color,
const doc::BrushRef& brush);
tools::ToolLoop* create_tool_loop_preview(
Editor* editor,
const doc::BrushRef& brush,
doc::Image* image,
const gfx::Point& celOrigin);
#endif // ENABLE_UI
//////////////////////////////////////////////////////////////////////
// For scripting
#ifdef ENABLE_SCRIPTING
tools::ToolLoop* create_tool_loop_for_script(
Context* context,
const Site& site,
ToolLoopParams& params);
#endif // ENABLE_SCRIPTING
} // namespace app
#endif

View File

@ -2870,7 +2870,7 @@ gfx::Rect Timeline::getRangeBounds(const Range& range) const
case Range::kLayers:
for (auto layer : range.selectedLayers()) {
layer_t layerIdx = getLayerIndex(layer);
rc |= getPartBounds(Hit(PART_ROW, layerIdx));
rc |= getPartBounds(Hit(PART_ROW_TEXT, layerIdx));
}
break;
}

View File

@ -1,6 +1,6 @@
# Aseprite Document Library
# Copyright (C) 2019 Igara Studio S.A.
# Copyright (C) 2001-2018 David Capello
# Copyright (C) 2019-2020 Igara Studio S.A.
# Copyright (C) 2001-2018 David Capello
if(WIN32)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
@ -29,6 +29,7 @@ add_library(doc-lib
cel_data_io.cpp
cel_io.cpp
cels_range.cpp
color.cpp
compressed_image.cpp
conversion_to_surface.cpp
document.cpp

View File

@ -224,11 +224,13 @@ TEST(Polygon, Triangle1Test)
TEST(Polygon, Triangle2Test)
{
// P1
// / \
// / \
// / \
// P0-----P2
/*
P1
/ \
/ \
/ \
P0-----P2
*/
int points[6] = { 0 , 4 ,
2 , 0 ,
4 , 4 ,
@ -262,11 +264,13 @@ TEST(Polygon, Triangle2Test)
TEST(Polygon, Triangle3Test)
{
// P2
// / \
// / \
// / \
// P0-----P1
/*
P2
/ \
/ \
/ \
P0-----P1
*/
int points[6] = { 0 , 4 ,
4 , 4 ,
2 , 0 ,
@ -300,11 +304,13 @@ TEST(Polygon, Triangle3Test)
TEST(Polygon, Triangle4Test)
{
// P2
// / \
// / \
// / \
// P1-----P0
/*
P2
/ \
/ \
/ \
P1-----P0
*/
int points[6] = { 4 , 4 ,
0 , 4 ,
2 , 0 ,
@ -408,17 +414,18 @@ TEST(Polygon, Poligon1Test)
TEST(Polygon, Polygon2Test)
{
// P3------P4
// P0 | /
// \ | /
// \ | /
// \ / P2 P5
// P1 / \
// \
// P9 /P7 \
// \ / \ \
// P8 \ P6
/*
P3------P4
P0 | /
\ | /
\ | /
\ / P2 P5
P1 / \
\
P9 /P7 \
\ / \ \
P8 \ P6
*/
int points[20] = { 0 , 1 ,
2 , 4 ,
4 , 3 ,
@ -467,23 +474,23 @@ TEST(Polygon, Polygon2Test)
EXPECT_EQ(results.scanLines[7].x1, 1);
EXPECT_EQ(results.scanLines[7].x2, 7);
EXPECT_EQ(results.scanLines[7].y, 4);
EXPECT_EQ(results.scanLines[8].x1, 2);
EXPECT_EQ(results.scanLines[8].x2, 8);
EXPECT_EQ(results.scanLines[8].y, 5);
EXPECT_EQ(results.scanLines[9].x1, 2);
EXPECT_EQ(results.scanLines[9].x2, 4);
EXPECT_EQ(results.scanLines[9].y, 6);
EXPECT_EQ(results.scanLines[10].x1, 7);
EXPECT_EQ(results.scanLines[10].x2, 8);
EXPECT_EQ(results.scanLines[10].y, 6);
EXPECT_EQ(results.scanLines[11].x1, 3);
EXPECT_EQ(results.scanLines[11].x2, 3);
EXPECT_EQ(results.scanLines[11].y, 7);
EXPECT_EQ(results.scanLines[12].x1, 9);
EXPECT_EQ(results.scanLines[12].x2, 9);
EXPECT_EQ(results.scanLines[12].y, 7);
@ -768,7 +775,7 @@ TEST(createUnion, testC8)
pairs.push_back(2);
pairs.push_back(3);
pairs.push_back(4);
EXPECT_EQ(doc::algorithm::createUnion(pairs, x, ints), true);
EXPECT_EQ(pairs[0], 0);
EXPECT_EQ(pairs[1], 4);

46
src/doc/color.cpp Normal file
View File

@ -0,0 +1,46 @@
// Aseprite Document Library
// Copyright (c) 2020 Igara Studio S.A.
//
// 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/color.h"
#include <algorithm>
namespace doc {
color_t rgba_to_graya_using_hsv(const color_t c)
{
const uint8_t M = std::max(rgba_getr(c),
std::max(rgba_getg(c),
rgba_getb(c)));
return graya(M,
rgba_geta(c));
}
color_t rgba_to_graya_using_hsl(const color_t c)
{
const int m = std::min(rgba_getr(c),
std::min(rgba_getg(c),
rgba_getb(c)));
const int M = std::max(rgba_getr(c),
std::max(rgba_getg(c),
rgba_getb(c)));
return graya((M + m) / 2,
rgba_geta(c));
}
color_t rgba_to_graya_using_luma(const color_t c)
{
return graya(rgb_luma(rgba_getr(c),
rgba_getg(c),
rgba_getb(c)),
rgba_geta(c));
}
} // namespace doc

View File

@ -11,8 +11,6 @@
#include "base/ints.h"
#include <algorithm>
namespace doc {
// The greatest int type to storage a color for an image in the
@ -95,31 +93,9 @@ namespace doc {
typedef color_t (*rgba_to_graya_func)(const color_t c);
inline color_t rgba_to_graya_using_hsv(const color_t c) {
const uint8_t M = std::max(rgba_getr(c),
std::max(rgba_getg(c),
rgba_getb(c)));
return graya(M,
rgba_geta(c));
}
inline color_t rgba_to_graya_using_hsl(const color_t c) {
const int m = std::min(rgba_getr(c),
std::min(rgba_getg(c),
rgba_getb(c)));
const int M = std::max(rgba_getr(c),
std::max(rgba_getg(c),
rgba_getb(c)));
return graya((M + m) / 2,
rgba_geta(c));
}
inline color_t rgba_to_graya_using_luma(const color_t c) {
return graya(rgb_luma(rgba_getr(c),
rgba_getg(c),
rgba_getb(c)),
rgba_geta(c));
}
color_t rgba_to_graya_using_hsv(const color_t c);
color_t rgba_to_graya_using_hsl(const color_t c);
color_t rgba_to_graya_using_luma(const color_t c);
} // namespace doc