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;