diff --git a/data/gui.xml b/data/gui.xml
index 38bd96ab9..307cc03c4 100644
--- a/data/gui.xml
+++ b/data/gui.xml
@@ -449,6 +449,7 @@
+
@@ -1144,6 +1145,14 @@
pointshape="floodfill"
tracepolicy="accumulate"
/>
+
diff --git a/data/themes/default/sheet.png b/data/themes/default/sheet.png
index d8540adb7..89835ed04 100644
Binary files a/data/themes/default/sheet.png and b/data/themes/default/sheet.png differ
diff --git a/data/themes/default/theme.xml b/data/themes/default/theme.xml
index bb63e7727..010e446d8 100644
--- a/data/themes/default/theme.xml
+++ b/data/themes/default/theme.xml
@@ -376,6 +376,7 @@
+
diff --git a/src/app/tools/ink.h b/src/app/tools/ink.h
index fbde6d2d9..7738477a4 100644
--- a/src/app/tools/ink.h
+++ b/src/app/tools/ink.h
@@ -8,6 +8,8 @@
#define APP_TOOLS_INK_H_INCLUDED
#pragma once
+#include "app/tools/stroke.h"
+
namespace gfx {
class Region;
}
@@ -22,9 +24,6 @@ namespace app {
// The main task of this class is to draw scanlines through its
// inkHline function member.
class Ink {
- // selection, paint, paint_fg, paint_bg, eraser,
- // replace_fg_with_bg, replace_bg_with_fg, pick_fg, pick_bg, scroll,
- // move, shade, blur, jumble
public:
virtual ~Ink() { }
@@ -89,6 +88,12 @@ namespace app {
// to a shape (e.g. pen shape) with various scanlines.
virtual void inkHline(int x1, int y, int x2, ToolLoop* loop) = 0;
+ // Returns true in case that the ink needs to update something
+ // depending on the specific stroke points (e.g. for color
+ // gradients)
+ virtual bool dependsOnStroke() const { return false; }
+ virtual void updateInk(ToolLoop* loop, Strokes& strokes) { }
+
};
} // namespace tools
diff --git a/src/app/tools/ink_processing.h b/src/app/tools/ink_processing.h
index 91318f0b5..8dcbb1e42 100644
--- a/src/app/tools/ink_processing.h
+++ b/src/app/tools/ink_processing.h
@@ -33,6 +33,7 @@ class BaseInkProcessing {
public:
virtual ~BaseInkProcessing() { }
virtual void hline(int x1, int y, int x2, ToolLoop* loop) = 0;
+ virtual void updateInk(ToolLoop* loop, Strokes& strokes) { }
};
template
@@ -193,6 +194,7 @@ public:
c = m_palette->getEntry(c);
color_t result = rgba_blender_normal(c, m_color, m_opacity);
+ // TODO should we use m_rgbmap->mapColor instead?
*m_dstAddress = m_palette->findBestfit(
rgba_getr(result),
rgba_getg(result),
@@ -865,6 +867,272 @@ private:
bool m_left;
};
+//////////////////////////////////////////////////////////////////////
+// Gradient Ink
+//////////////////////////////////////////////////////////////////////
+
+static ImageBufferPtr tmpGradientBuffer; // TODO non-thread safe
+
+class TemporalPixmanGradient {
+public:
+ TemporalPixmanGradient(ToolLoop* loop) {
+ if (!tmpGradientBuffer)
+ tmpGradientBuffer.reset(new ImageBuffer(1));
+
+ m_tmpImage.reset(
+ Image::create(IMAGE_RGB,
+ loop->getDstImage()->width(),
+ loop->getDstImage()->height(),
+ tmpGradientBuffer));
+ m_tmpImage->clear(0);
+ }
+
+ void renderRgbaGradient(ToolLoop* loop, Strokes& strokes,
+ // RGBA colors
+ color_t c0, color_t c1) {
+ if (strokes.empty() || strokes[0].size() < 2) {
+ m_tmpImage->clear(0);
+ return;
+ }
+
+ pixman_point_fixed_t u, v;
+ pixman_gradient_stop_t stops[2];
+
+ u.x = pixman_int_to_fixed(strokes[0].firstPoint().x);
+ u.y = pixman_int_to_fixed(strokes[0].firstPoint().y);
+ v.x = pixman_int_to_fixed(strokes[0].lastPoint().x);
+ v.y = pixman_int_to_fixed(strokes[0].lastPoint().y);
+
+ // As we use non-premultiplied RGB values, we need correct RGB
+ // values on each stop. So in case that one color has alpha=0
+ // (complete transparent), use the RGB values of the
+ // non-transparent color in the other stop point.
+ if (doc::rgba_geta(c0) == 0 &&
+ doc::rgba_geta(c1) != 0) {
+ c0 = (c1 & rgba_rgb_mask);
+ }
+ else if (doc::rgba_geta(c0) != 0 &&
+ doc::rgba_geta(c1) == 0) {
+ c1 = (c0 & rgba_rgb_mask);
+ }
+
+ stops[0].x = pixman_int_to_fixed(0);
+ stops[0].color.red = int(doc::rgba_getr(c0)) << 8;
+ stops[0].color.green = int(doc::rgba_getg(c0)) << 8;
+ stops[0].color.blue = int(doc::rgba_getb(c0)) << 8;
+ stops[0].color.alpha = int(doc::rgba_geta(c0)) << 8;
+
+ stops[1].x = pixman_int_to_fixed(1);
+ stops[1].color.red = int(doc::rgba_getr(c1)) << 8;
+ stops[1].color.green = int(doc::rgba_getg(c1)) << 8;
+ stops[1].color.blue = int(doc::rgba_getb(c1)) << 8;
+ stops[1].color.alpha = int(doc::rgba_geta(c1)) << 8;
+
+ pixman_image_t* gradientImg =
+ pixman_image_create_linear_gradient(
+ &u, &v, stops, 2);
+ pixman_image_set_repeat(gradientImg, PIXMAN_REPEAT_PAD);
+
+ pixman_image_t* rasterImg =
+ pixman_image_create_bits(PIXMAN_a8b8g8r8,
+ m_tmpImage->width(),
+ m_tmpImage->height(),
+ (uint32_t*)m_tmpImage->getPixelAddress(0, 0),
+ m_tmpImage->getRowStrideSize());
+
+ pixman_image_composite(PIXMAN_OP_SRC, // Copy the gradient (no alpha compositing)
+ gradientImg, nullptr,
+ rasterImg,
+ 0, 0, 0, 0, 0, 0,
+ m_tmpImage->width(),
+ m_tmpImage->height());
+
+ pixman_image_unref(gradientImg);
+ pixman_image_unref(rasterImg);
+ }
+
+protected:
+ ImageRef m_tmpImage;
+ RgbTraits::address_t m_tmpAddress;
+};
+
+template
+class GradientInkProcessing : public DoubleInkProcessing, ImageTraits> {
+public:
+ GradientInkProcessing(ToolLoop* loop) {
+ }
+
+ void processPixel(int x, int y) {
+ // Do nothing (it's specialized for each case)
+ }
+};
+
+template<>
+class GradientInkProcessing : public TemporalPixmanGradient,
+ public DoubleInkProcessing, RgbTraits> {
+public:
+ typedef DoubleInkProcessing, RgbTraits> base;
+
+ GradientInkProcessing(ToolLoop* loop)
+ : TemporalPixmanGradient(loop)
+ , m_opacity(loop->getOpacity()) {
+ }
+
+ void updateInk(ToolLoop* loop, Strokes& strokes) override {
+ color_t c0 = loop->getPrimaryColor();
+ color_t c1 = loop->getSecondaryColor();
+
+ renderRgbaGradient(loop, strokes, c0, c1);
+ }
+
+ void hline(int x1, int y, int x2, ToolLoop* loop) override {
+ m_tmpAddress = (RgbTraits::address_t)m_tmpImage->getPixelAddress(x1, y);
+ base::hline(x1, y, x2, loop);
+ }
+
+ void processPixel(int x, int y) {
+ // As m_tmpAddress is the rendered gradient from pixman, its RGB
+ // values are premultiplied, here we can divide them by the alpha
+ // value to get the non-premultiplied values.
+ doc::color_t c = *m_tmpAddress;
+ int a = doc::rgba_geta(c);
+ int r, g, b;
+ if (a > 0) {
+ r = doc::rgba_getr(c) * 255 / a;
+ g = doc::rgba_getg(c) * 255 / a;
+ b = doc::rgba_getb(c) * 255 / a;
+ }
+ else
+ r = g = b = 0;
+
+ *m_dstAddress = rgba_blender_normal(*m_srcAddress,
+ doc::rgba(r, g, b, a),
+ m_opacity);
+ ++m_tmpAddress;
+ }
+
+private:
+ const int m_opacity;
+};
+
+
+template<>
+class GradientInkProcessing : public TemporalPixmanGradient,
+ public DoubleInkProcessing, GrayscaleTraits> {
+public:
+ typedef DoubleInkProcessing, GrayscaleTraits> base;
+
+ GradientInkProcessing(ToolLoop* loop)
+ : TemporalPixmanGradient(loop)
+ , m_opacity(loop->getOpacity()) {
+ }
+
+ void updateInk(ToolLoop* loop, Strokes& strokes) override {
+ color_t c0 = loop->getPrimaryColor();
+ color_t c1 = loop->getSecondaryColor();
+ int v0 = int(doc::graya_getv(c0));
+ int a0 = int(doc::graya_geta(c0));
+ int v1 = int(doc::graya_getv(c1));
+ int a1 = int(doc::graya_geta(c1));
+ c0 = doc::rgba(v0, v0, v0, a0);
+ c1 = doc::rgba(v1, v1, v1, a1);
+
+ renderRgbaGradient(loop, strokes, c0, c1);
+ }
+
+ void hline(int x1, int y, int x2, ToolLoop* loop) override {
+ m_tmpAddress = (RgbTraits::address_t)m_tmpImage->getPixelAddress(x1, y);
+ base::hline(x1, y, x2, loop);
+ }
+
+ void processPixel(int x, int y) {
+ // As m_tmpAddress is the rendered gradient from pixman, its RGB
+ // values are premultiplied, here we can divide them by the alpha
+ // value to get the non-premultiplied values.
+ doc::color_t c = *m_tmpAddress;
+ int a = doc::rgba_geta(c);
+ int v;
+ if (a > 0) {
+ // Here we could get R, G, or B because this is a grayscale gradient anyway.
+ v = doc::rgba_getr(c) * 255 / a;
+ }
+ else
+ v = 0;
+
+ *m_dstAddress = graya_blender_normal(*m_srcAddress,
+ doc::graya(v, a),
+ m_opacity);
+ ++m_tmpAddress;
+ }
+
+private:
+ const int m_opacity;
+};
+
+
+template<>
+class GradientInkProcessing : public TemporalPixmanGradient,
+ public DoubleInkProcessing, IndexedTraits> {
+public:
+ typedef DoubleInkProcessing, IndexedTraits> base;
+
+ GradientInkProcessing(ToolLoop* loop)
+ : TemporalPixmanGradient(loop)
+ , m_opacity(loop->getOpacity())
+ , m_palette(get_current_palette())
+ , m_rgbmap(loop->getRgbMap())
+ , m_maskIndex(loop->getLayer()->isBackground() ? -1: loop->sprite()->transparentColor()) {
+ }
+
+ void updateInk(ToolLoop* loop, Strokes& strokes) override {
+ color_t c0 = m_palette->getEntry(loop->getPrimaryColor());
+ color_t c1 = m_palette->getEntry(loop->getSecondaryColor());
+
+ renderRgbaGradient(loop, strokes, c0, c1);
+ }
+
+ void hline(int x1, int y, int x2, ToolLoop* loop) override {
+ m_tmpAddress = (RgbTraits::address_t)m_tmpImage->getPixelAddress(x1, y);
+ base::hline(x1, y, x2, loop);
+ }
+
+ void processPixel(int x, int y) {
+ // As m_tmpAddress is the rendered gradient from pixman, its RGB
+ // values are premultiplied, here we can divide them by the alpha
+ // value to get the non-premultiplied values.
+ doc::color_t c = *m_tmpAddress;
+ int a = doc::rgba_geta(c);
+ int r, g, b;
+ if (a > 0) {
+ r = doc::rgba_getr(c) * 255 / a;
+ g = doc::rgba_getg(c) * 255 / a;
+ b = doc::rgba_getb(c) * 255 / a;
+ }
+ else
+ r = g = b = 0;
+
+ doc::color_t c0 = *m_srcAddress;
+ if (int(c0) == m_maskIndex)
+ c0 = m_palette->getEntry(c0) & rgba_rgb_mask; // Alpha = 0
+ else
+ c0 = m_palette->getEntry(c0);
+ c = rgba_blender_normal(c0, c, m_opacity);
+
+ *m_dstAddress = m_rgbmap->mapColor(rgba_getr(c),
+ rgba_getg(c),
+ rgba_getb(c),
+ rgba_geta(c));
+ ++m_tmpAddress;
+ }
+
+private:
+ const int m_opacity;
+ const Palette* m_palette;
+ const RgbMap* m_rgbmap;
+ const int m_maskIndex;
+};
+
+
//////////////////////////////////////////////////////////////////////
// Xor Ink
//////////////////////////////////////////////////////////////////////
diff --git a/src/app/tools/inks.h b/src/app/tools/inks.h
index 6a91db227..d1df5b7cb 100644
--- a/src/app/tools/inks.h
+++ b/src/app/tools/inks.h
@@ -34,6 +34,10 @@ protected:
m_proc.reset(proc);
}
+ BaseInkProcessing* proc() {
+ return m_proc;
+ }
+
private:
InkProcessingPtr m_proc;
};
@@ -133,6 +137,25 @@ public:
};
+class GradientInk : public BaseInk {
+public:
+ Ink* clone() override { return new GradientInk(*this); }
+
+ bool isPaint() const override { return true; }
+ bool isEffect() const override { return true; }
+ bool dependsOnStroke() const override { return true; }
+
+ void prepareInk(ToolLoop* loop) override {
+ setProc(get_ink_proc(loop));
+ }
+
+ void updateInk(ToolLoop* loop, Strokes& strokes) override {
+ proc()->updateInk(loop, strokes);
+ }
+
+};
+
+
class ScrollInk : public Ink {
public:
Ink* clone() override { return new ScrollInk(*this); }
diff --git a/src/app/tools/intertwiners.h b/src/app/tools/intertwiners.h
index 0cd62fe10..101229cd6 100644
--- a/src/app/tools/intertwiners.h
+++ b/src/app/tools/intertwiners.h
@@ -1,5 +1,5 @@
// Aseprite
-// Copyright (C) 2001-2015 David Capello
+// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@@ -20,6 +20,19 @@ public:
}
};
+class IntertwineFirstPoint : public Intertwine {
+public:
+
+ void joinStroke(ToolLoop* loop, const Stroke& stroke) override {
+ if (!stroke.empty())
+ doPointshapePoint(stroke[0].x, stroke[0].y, loop);
+ }
+
+ void fillStroke(ToolLoop* loop, const Stroke& stroke) override {
+ joinStroke(loop, stroke);
+ }
+};
+
class IntertwineAsLines : public Intertwine {
public:
bool snapByAngle() override { return true; }
diff --git a/src/app/tools/tool_box.cpp b/src/app/tools/tool_box.cpp
index 0558193b7..9b4c85546 100644
--- a/src/app/tools/tool_box.cpp
+++ b/src/app/tools/tool_box.cpp
@@ -8,6 +8,8 @@
#include "config.h"
#endif
+#include // Needed for GradientInk
+
#include "app/tools/tool_box.h"
#include "app/gui_xml.h"
@@ -55,6 +57,7 @@ const char* WellKnownInks::PaintBg = "paint_bg";
const char* WellKnownInks::PaintCopy = "paint_copy";
const char* WellKnownInks::PaintLockAlpha = "paint_lock_alpha";
const char* WellKnownInks::Shading = "shading";
+const char* WellKnownInks::Gradient = "gradient";
const char* WellKnownInks::Eraser = "eraser";
const char* WellKnownInks::ReplaceFgWithBg = "replace_fg_with_bg";
const char* WellKnownInks::ReplaceBgWithFg = "replace_bg_with_fg";
@@ -69,6 +72,7 @@ const char* WellKnownInks::Blur = "blur";
const char* WellKnownInks::Jumble = "jumble";
const char* WellKnownIntertwiners::None = "none";
+const char* WellKnownIntertwiners::FirstPoint = "first_point";
const char* WellKnownIntertwiners::AsLines = "as_lines";
const char* WellKnownIntertwiners::AsRectangles = "as_rectangles";
const char* WellKnownIntertwiners::AsEllipses = "as_ellipses";
@@ -89,6 +93,7 @@ ToolBox::ToolBox()
m_inks[WellKnownInks::PaintBg] = new PaintInk(PaintInk::WithBg);
m_inks[WellKnownInks::PaintCopy] = new PaintInk(PaintInk::Copy);
m_inks[WellKnownInks::PaintLockAlpha] = new PaintInk(PaintInk::LockAlpha);
+ m_inks[WellKnownInks::Gradient] = new GradientInk();
m_inks[WellKnownInks::Shading] = new ShadingInk();
m_inks[WellKnownInks::Eraser] = new EraserInk(EraserInk::Eraser);
m_inks[WellKnownInks::ReplaceFgWithBg] = new EraserInk(EraserInk::ReplaceFgWithBg);
@@ -115,6 +120,7 @@ ToolBox::ToolBox()
m_pointshapers[WellKnownPointShapes::Spray] = new SprayPointShape();
m_intertwiners[WellKnownIntertwiners::None] = new IntertwineNone();
+ m_intertwiners[WellKnownIntertwiners::FirstPoint] = new IntertwineFirstPoint();
m_intertwiners[WellKnownIntertwiners::AsLines] = new IntertwineAsLines();
m_intertwiners[WellKnownIntertwiners::AsRectangles] = new IntertwineAsRectangles();
m_intertwiners[WellKnownIntertwiners::AsEllipses] = new IntertwineAsEllipses();
diff --git a/src/app/tools/tool_box.h b/src/app/tools/tool_box.h
index fb86e0b65..22d983bef 100644
--- a/src/app/tools/tool_box.h
+++ b/src/app/tools/tool_box.h
@@ -37,6 +37,7 @@ namespace app {
extern const char* PaintCopy;
extern const char* PaintLockAlpha;
extern const char* Shading;
+ extern const char* Gradient;
extern const char* Eraser;
extern const char* ReplaceFgWithBg;
extern const char* ReplaceBgWithFg;
@@ -53,6 +54,7 @@ namespace app {
namespace WellKnownIntertwiners {
extern const char* None;
+ extern const char* FirstPoint;
extern const char* AsLines;
extern const char* AsRectangles;
extern const char* AsEllipses;
diff --git a/src/app/tools/tool_loop_manager.cpp b/src/app/tools/tool_loop_manager.cpp
index 1876ea86d..42739d5a0 100644
--- a/src/app/tools/tool_loop_manager.cpp
+++ b/src/app/tools/tool_loop_manager.cpp
@@ -173,6 +173,9 @@ void ToolLoopManager::doLoopStep(bool last_step)
m_toolLoop->validateSrcImage(m_dirtyArea);
}
+ if (m_toolLoop->getInk()->dependsOnStroke())
+ m_toolLoop->getInk()->updateInk(m_toolLoop, strokes);
+
// Invalidate destionation image areas.
if (m_toolLoop->getTracePolicy() == TracePolicy::Last) {
// Copy source to destination (reset the previous trace). Useful
diff --git a/src/app/ui/editor/brush_preview.cpp b/src/app/ui/editor/brush_preview.cpp
index 21eb3423a..895d15e9e 100644
--- a/src/app/ui/editor/brush_preview.cpp
+++ b/src/app/ui/editor/brush_preview.cpp
@@ -1,5 +1,5 @@
// Aseprite
-// Copyright (C) 2001-2016 David Capello
+// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@@ -130,7 +130,7 @@ void BrushPreview::show(const gfx::Point& screenPos)
}
else if (
(brush->type() == kImageBrushType ||
- brush->size() > 1.0 / m_editor->zoom().scale()) &&
+ ((isFloodfill ? 1: brush->size()) > (1.0 / m_editor->zoom().scale()))) &&
(// Use cursor bounds for inks that are effects (eraser, blur, etc.)
(ink->isEffect()) ||
// or when the brush color is transparent and we are not in the background layer
diff --git a/src/gfx/region.h b/src/gfx/region.h
index 8678b22c0..568168c7b 100644
--- a/src/gfx/region.h
+++ b/src/gfx/region.h
@@ -1,5 +1,5 @@
// Aseprite Gfx Library
-// Copyright (C) 2001-2016 David Capello
+// Copyright (C) 2001-2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@@ -26,10 +26,6 @@ namespace gfx {
#else
struct Box {
int32_t x1, y1, x2, y2;
-
- operator Rect() const {
- return Rect(x1, y1, x2-x1, y2-y1);
- }
};
struct Region {
Box extents;
@@ -51,7 +47,13 @@ namespace gfx {
RegionIterator operator++(int) { RegionIterator o(*this); ++m_ptr; return o; }
bool operator==(const RegionIterator& o) const { return m_ptr == o.m_ptr; }
bool operator!=(const RegionIterator& o) const { return m_ptr != o.m_ptr; }
- reference operator*() { m_rect = *m_ptr; return m_rect; }
+ reference operator*() {
+ m_rect.x = m_ptr->x1;
+ m_rect.y = m_ptr->y1;
+ m_rect.w = m_ptr->x2 - m_ptr->x1;
+ m_rect.h = m_ptr->y2 - m_ptr->y1;
+ return m_rect;
+ }
private:
Box* m_ptr;
mutable Rect m_rect;