Add gradient tool (fix #418)

This commit is contained in:
David Capello 2017-05-04 10:38:34 -03:00
parent 212c3e5126
commit 97bfaa21ec
12 changed files with 344 additions and 12 deletions

View File

@ -449,6 +449,7 @@
<key tool="zoom" shortcut="Z" />
<key tool="paint_bucket" shortcut="G" />
<key tool="gradient" shortcut="G" />
<key tool="line" shortcut="L" />
<key tool="curve" shortcut="Shift+L" />
@ -1144,6 +1145,14 @@
pointshape="floodfill"
tracepolicy="accumulate"
/>
<tool id="gradient"
text="Gradient Tool"
ink="gradient"
controller="two_points"
pointshape="floodfill"
intertwine="first_point"
tracepolicy="last"
/>
</group>
<group id="perfect_traces" text="Perfect Traces">

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -376,6 +376,7 @@
<part id="tool_zoom" x="208" y="32" w="16" h="16" />
<part id="tool_slice" x="224" y="32" w="16" h="16" />
<part id="tool_paint_bucket" x="144" y="48" w="16" h="16" />
<part id="tool_gradient" x="160" y="48" w="16" h="16" />
<part id="tool_line" x="144" y="64" w="16" h="16" />
<part id="tool_curve" x="160" y="64" w="16" h="16" />
<part id="tool_rectangle" x="144" y="80" w="16" h="16" />

View File

@ -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

View File

@ -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<typename Derived>
@ -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<typename ImageTraits>
class GradientInkProcessing : public DoubleInkProcessing<GradientInkProcessing<ImageTraits>, ImageTraits> {
public:
GradientInkProcessing(ToolLoop* loop) {
}
void processPixel(int x, int y) {
// Do nothing (it's specialized for each case)
}
};
template<>
class GradientInkProcessing<RgbTraits> : public TemporalPixmanGradient,
public DoubleInkProcessing<GradientInkProcessing<RgbTraits>, RgbTraits> {
public:
typedef DoubleInkProcessing<GradientInkProcessing<RgbTraits>, 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<GrayscaleTraits> : public TemporalPixmanGradient,
public DoubleInkProcessing<GradientInkProcessing<GrayscaleTraits>, GrayscaleTraits> {
public:
typedef DoubleInkProcessing<GradientInkProcessing<GrayscaleTraits>, 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<IndexedTraits> : public TemporalPixmanGradient,
public DoubleInkProcessing<GradientInkProcessing<IndexedTraits>, IndexedTraits> {
public:
typedef DoubleInkProcessing<GradientInkProcessing<IndexedTraits>, 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
//////////////////////////////////////////////////////////////////////

View File

@ -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<GradientInkProcessing>(loop));
}
void updateInk(ToolLoop* loop, Strokes& strokes) override {
proc()->updateInk(loop, strokes);
}
};
class ScrollInk : public Ink {
public:
Ink* clone() override { return new ScrollInk(*this); }

View File

@ -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; }

View File

@ -8,6 +8,8 @@
#include "config.h"
#endif
#include <pixman.h> // 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();

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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;