Add dithering options for Gradient tool (#418)

This commit is contained in:
David Capello 2017-05-23 20:41:30 -03:00
parent adbcc75fad
commit ae67cab015
11 changed files with 319 additions and 233 deletions

View File

@ -64,6 +64,9 @@ namespace app {
// Returns true if this ink is used to mark slices
virtual bool isSlice() const { return false; }
// Returns true if this tool uses the dithering options
virtual bool withDitheringOptions() const { return false; }
// Returns true if inkHline() needs source cel coordinates
// instead of sprite coordinates (i.e. relative to
// ToolLoop::getCelOrigin()).

View File

@ -16,6 +16,7 @@
#include "filters/neighboring_pixels.h"
#include "gfx/hsv.h"
#include "gfx/rgb.h"
#include "render/dithering.h"
namespace app {
namespace tools {
@ -957,138 +958,19 @@ protected:
};
template<typename ImageTraits>
class GradientInkProcessing : public DoubleInkProcessing<GradientInkProcessing<ImageTraits>, ImageTraits> {
class GradientInkProcessing : public TemporalPixmanGradient,
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;
typedef DoubleInkProcessing<GradientInkProcessing<ImageTraits>, ImageTraits> 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);
, m_maskIndex(loop->getLayer()->isBackground() ? -1: loop->sprite()->transparentColor())
, m_matrix(loop->getDitheringMatrix())
, m_dither(loop->getDitheringAlgorithm()) {
}
void hline(int x1, int y, int x2, ToolLoop* loop) override {
@ -1096,33 +978,12 @@ public:
base::hline(x1, y, x2, loop);
}
void updateInk(ToolLoop* loop, Strokes& strokes) override {
// Do nothing
}
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;
// Do nothing (it's specialized for each case)
}
private:
@ -1130,8 +991,134 @@ private:
const Palette* m_palette;
const RgbMap* m_rgbmap;
const int m_maskIndex;
const render::DitheringMatrix m_matrix;
render::DitheringAlgorithmBase* m_dither;
};
template<>
void GradientInkProcessing<RgbTraits>::updateInk(ToolLoop* loop, Strokes& strokes)
{
color_t c0 = loop->getPrimaryColor();
color_t c1 = loop->getSecondaryColor();
renderRgbaGradient(loop, strokes, c0, c1);
}
template<>
void GradientInkProcessing<RgbTraits>::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;
c = rgba_blender_normal(*m_srcAddress,
doc::rgba(r, g, b, a),
m_opacity);
if (m_dither) {
int i = m_dither->ditherRgbPixelToIndex(
m_matrix, c, x, y, m_rgbmap, m_palette);
c = m_palette->getEntry(i);
}
*m_dstAddress = c;
++m_tmpAddress;
}
template<>
void GradientInkProcessing<GrayscaleTraits>::updateInk(ToolLoop* loop, Strokes& strokes)
{
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);
}
template<>
void GradientInkProcessing<GrayscaleTraits>::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;
}
template<>
void GradientInkProcessing<IndexedTraits>::updateInk(ToolLoop* loop, Strokes& strokes)
{
color_t c0 = m_palette->getEntry(loop->getPrimaryColor());
color_t c1 = m_palette->getEntry(loop->getSecondaryColor());
renderRgbaGradient(loop, strokes, c0, c1);
}
template<>
void GradientInkProcessing<IndexedTraits>::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);
if (m_dither) {
*m_dstAddress = m_dither->ditherRgbPixelToIndex(
m_matrix, c, x, y, m_rgbmap, m_palette);
}
else {
*m_dstAddress = m_rgbmap->mapColor(rgba_getr(c),
rgba_getg(c),
rgba_getb(c),
rgba_geta(c));
}
++m_tmpAddress;
}
//////////////////////////////////////////////////////////////////////
// Xor Ink

View File

@ -144,6 +144,7 @@ public:
bool isPaint() const override { return true; }
bool isEffect() const override { return true; }
bool dependsOnStroke() const override { return true; }
bool withDitheringOptions() const override { return true; }
void prepareInk(ToolLoop* loop) override {
setProc(get_ink_proc<GradientInkProcessing>(loop));

View File

@ -30,6 +30,11 @@ namespace doc {
class Sprite;
}
namespace render {
class DitheringAlgorithmBase;
class DitheringMatrix;
}
namespace app {
class Context;
class Document;
@ -222,6 +227,10 @@ namespace app {
virtual void updateDirtyArea() = 0;
virtual void updateStatusBar(const char* text) = 0;
// For gradients
virtual render::DitheringMatrix getDitheringMatrix() = 0;
virtual render::DitheringAlgorithmBase* getDitheringAlgorithm() = 0;
};
} // namespace tools

View File

@ -32,6 +32,7 @@
#include "app/ui/brush_popup.h"
#include "app/ui/button_set.h"
#include "app/ui/color_button.h"
#include "app/ui/dithering_selector.h"
#include "app/ui/icon_button.h"
#include "app/ui/skin/skin_theme.h"
#include "app/ui_context.h"
@ -44,6 +45,7 @@
#include "doc/palette.h"
#include "doc/remap.h"
#include "obs/connection.h"
#include "render/dithering.h"
#include "she/surface.h"
#include "she/system.h"
#include "ui/button.h"
@ -1378,6 +1380,8 @@ ContextBar::ContextBar()
addChild(m_tolerance = new ToleranceField());
addChild(m_contiguous = new ContiguousField());
addChild(m_paintBucketSettings = new PaintBucketSettingsField());
addChild(m_ditheringSelector = new DitheringSelector());
m_ditheringSelector->setUseCustomWidget(false); // Disable custom widget because the context bar is too small
addChild(m_inkType = new InkTypeField(this));
addChild(m_inkOpacityLabel = new Label("Opacity:"));
@ -1628,65 +1632,69 @@ void ContextBar::updateForTool(tools::Tool* tool)
m_spraySpeed->setValue(toolPref->spray.speed());
}
bool updateShade = (!m_inkShades->isVisible() && hasInkShades);
const bool updateShade = (!m_inkShades->isVisible() && hasInkShades);
m_eyedropperField->updateFromPreferences(preferences.eyedropper);
m_autoSelectLayer->setSelected(preferences.editor.autoSelectLayer());
// True if we have an image as brush
bool hasImageBrush = (activeBrush()->type() == kImageBrushType);
const bool hasImageBrush = (activeBrush()->type() == kImageBrushType);
// True if the brush type supports angle.
bool hasBrushWithAngle =
const bool hasBrushWithAngle =
(activeBrush()->size() > 1) &&
(activeBrush()->type() == kSquareBrushType ||
activeBrush()->type() == kLineBrushType);
// True if the current tool is eyedropper.
bool needZoomButtons = tool &&
const bool needZoomButtons = tool &&
(tool->getInk(0)->isZoom() ||
tool->getInk(1)->isZoom() ||
tool->getInk(0)->isScrollMovement() ||
tool->getInk(1)->isScrollMovement());
// True if the current tool is eyedropper.
bool isEyedropper = tool &&
const bool isEyedropper = tool &&
(tool->getInk(0)->isEyedropper() ||
tool->getInk(1)->isEyedropper());
// True if the current tool is move tool.
bool isMove = tool &&
const bool isMove = tool &&
(tool->getInk(0)->isCelMovement() ||
tool->getInk(1)->isCelMovement());
// True if the current tool is floodfill
bool isFloodfill = tool &&
const bool isFloodfill = tool &&
(tool->getPointShape(0)->isFloodFill() ||
tool->getPointShape(1)->isFloodFill());
// True if the current tool needs tolerance options
bool hasTolerance = tool &&
const bool hasTolerance = tool &&
(tool->getPointShape(0)->isFloodFill() ||
tool->getPointShape(1)->isFloodFill());
// True if the current tool needs spray options
bool hasSprayOptions = tool &&
const bool hasSprayOptions = tool &&
(tool->getPointShape(0)->isSpray() ||
tool->getPointShape(1)->isSpray());
bool hasSelectOptions = tool &&
const bool hasSelectOptions = tool &&
(tool->getInk(0)->isSelection() ||
tool->getInk(1)->isSelection());
bool isFreehand = tool &&
const bool isFreehand = tool &&
(tool->getController(0)->isFreehand() ||
tool->getController(1)->isFreehand());
bool showOpacity =
const bool showOpacity =
(supportOpacity) &&
((isPaint && (hasInkWithOpacity || hasImageBrush)) ||
(isEffect));
const bool withDithering =
(tool->getInk(0)->withDitheringOptions() ||
tool->getInk(1)->withDitheringOptions());
// Show/Hide fields
m_zoomButtons->setVisible(needZoomButtons);
m_brushType->setVisible(supportOpacity && (!isFloodfill || (isFloodfill && hasImageBrush)));
@ -1706,6 +1714,7 @@ void ContextBar::updateForTool(tools::Tool* tool)
m_paintBucketSettings->setVisible(hasTolerance);
m_sprayBox->setVisible(hasSprayOptions);
m_selectionOptionsBox->setVisible(hasSelectOptions);
m_ditheringSelector->setVisible(withDithering);
m_selectionMode->setVisible(true);
m_pivot->setVisible(true);
m_dropPixels->setVisible(false);
@ -1955,4 +1964,28 @@ void ContextBar::setInkType(tools::InkType type)
m_inkType->setInkType(type);
}
render::DitheringMatrix ContextBar::ditheringMatrix()
{
return m_ditheringSelector->ditheringMatrix();
}
render::DitheringAlgorithmBase* ContextBar::ditheringAlgorithm()
{
static base::UniquePtr<render::DitheringAlgorithmBase> s_dither;
switch (m_ditheringSelector->ditheringAlgorithm()) {
case render::DitheringAlgorithm::None:
s_dither.reset(nullptr);
break;
case render::DitheringAlgorithm::Ordered:
s_dither.reset(new render::OrderedDither2(-1));
break;
case render::DitheringAlgorithm::Old:
s_dither.reset(new render::OrderedDither(-1));
break;
}
return s_dither.get();
}
} // namespace app

View File

@ -26,19 +26,25 @@ namespace doc {
class Remap;
}
namespace ui {
class Box;
class Button;
class Label;
namespace render {
class DitheringAlgorithmBase;
class DitheringMatrix;
}
namespace tools {
class Tool;
}
namespace ui {
class Box;
class Button;
class Label;
}
namespace app {
class BrushSlot;
class DitheringSelector;
class ContextBar : public ui::Box
, public obs::observable<ContextBarObserver>
@ -70,6 +76,10 @@ namespace app {
void setInkType(tools::InkType type);
// For gradients
render::DitheringMatrix ditheringMatrix();
render::DitheringAlgorithmBase* ditheringAlgorithm();
// Signals
obs::signal<void()> BrushChange;
@ -134,6 +144,7 @@ namespace app {
SprayWidthField* m_sprayWidth;
SpraySpeedField* m_spraySpeed;
ui::Box* m_selectionOptionsBox;
DitheringSelector* m_ditheringSelector;
SelectionModeField* m_selectionMode;
TransparentColorField* m_transparentColor;
PivotField* m_pivot;

View File

@ -132,7 +132,7 @@ DitheringSelector::DitheringSelector()
render::BayerMatrix(2), "Old Dithering - Bayer Matrix 2x2"));
setSelectedItemIndex(0);
setMinSize(getItem(0)->sizeHint());
setSizeHint(getItem(0)->sizeHint());
}
render::DitheringAlgorithm DitheringSelector::ditheringAlgorithm()

View File

@ -47,6 +47,7 @@
#include "doc/remap.h"
#include "doc/slice.h"
#include "doc/sprite.h"
#include "render/dithering.h"
#include "render/render.h"
#include "ui/ui.h"
@ -265,6 +266,14 @@ public:
StatusBar::instance()->setStatusText(0, text);
}
render::DitheringMatrix getDitheringMatrix() override {
return App::instance()->contextBar()->ditheringMatrix();
}
render::DitheringAlgorithmBase* getDitheringAlgorithm() override {
return App::instance()->contextBar()->ditheringAlgorithm();
}
};
//////////////////////////////////////////////////////////////////////

15
src/render/dithering.h Normal file
View File

@ -0,0 +1,15 @@
// Aseprite Render Library
// Copyright (c) 2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef RENDER_DITHERING_H_INCLUDED
#define RENDER_DITHERING_H_INCLUDED
#pragma once
#include "render/dithering_algorithm.h"
#include "render/dithering_matrix.h"
#include "render/ordered_dither.h"
#endif

View File

@ -0,0 +1,82 @@
// Aseprite Render Library
// Copyright (c) 2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef RENDER_DITHERING_MATRIX_H_INCLUDED
#define RENDER_DITHERING_MATRIX_H_INCLUDED
#pragma once
#include <vector>
namespace render {
class DitheringMatrix {
public:
DitheringMatrix()
: m_rows(1), m_cols(1)
, m_matrix(1, 1)
, m_maxValue(1) {
}
DitheringMatrix(int rows, int cols)
: m_rows(rows), m_cols(cols)
, m_matrix(rows*cols, 0)
, m_maxValue(1) {
}
int rows() const { return m_rows; }
int cols() const { return m_cols; }
int maxValue() const { return m_maxValue; }
void calcMaxValue() {
m_maxValue = *std::max_element(m_matrix.begin(),
m_matrix.end());
m_maxValue = std::max(m_maxValue, 1);
}
int operator()(int i, int j) const {
return m_matrix[(i%m_rows)*m_cols + (j%m_cols)];
}
int& operator()(int i, int j) {
return m_matrix[(i%m_rows)*m_cols + (j%m_cols)];
}
private:
int m_rows, m_cols;
std::vector<int> m_matrix;
int m_maxValue;
};
// Creates a Bayer dither matrix.
class BayerMatrix : public DitheringMatrix {
static int D2[4];
public:
BayerMatrix(int n) : DitheringMatrix(n, n) {
for (int i=0; i<n; ++i)
for (int j=0; j<n; ++j)
operator()(i, j) = Dn(i, j, n);
calcMaxValue();
}
private:
int Dn(int i, int j, int n) const {
ASSERT(i >= 0 && i < n);
ASSERT(j >= 0 && j < n);
if (n == 2)
return D2[i*2 + j];
else
return
+ 4*Dn(i%(n/2), j%(n/2), n/2)
+ Dn(i/(n/2), j/(n/2), 2);
}
};
} // namespace render
#endif

View File

@ -12,6 +12,7 @@
#include "doc/image_impl.h"
#include "doc/palette.h"
#include "doc/rgbmap.h"
#include "render/dithering_matrix.h"
#include "render/task_delegate.h"
#include <algorithm>
@ -20,71 +21,6 @@
namespace render {
class DitheringMatrix {
public:
DitheringMatrix()
: m_rows(1), m_cols(1)
, m_matrix(1, 1)
, m_maxValue(1) {
}
DitheringMatrix(int rows, int cols)
: m_rows(rows), m_cols(cols)
, m_matrix(rows*cols, 0)
, m_maxValue(1) {
}
int rows() const { return m_rows; }
int cols() const { return m_cols; }
int maxValue() const { return m_maxValue; }
void calcMaxValue() {
m_maxValue = *std::max_element(m_matrix.begin(),
m_matrix.end());
m_maxValue = std::max(m_maxValue, 1);
}
int operator()(int i, int j) const {
return m_matrix[(i%m_rows)*m_cols + (j%m_cols)];
}
int& operator()(int i, int j) {
return m_matrix[(i%m_rows)*m_cols + (j%m_cols)];
}
private:
int m_rows, m_cols;
std::vector<int> m_matrix;
int m_maxValue;
};
// Creates a Bayer dither matrix.
class BayerMatrix : public DitheringMatrix {
static int D2[4];
public:
BayerMatrix(int n) : DitheringMatrix(n, n) {
for (int i=0; i<n; ++i)
for (int j=0; j<n; ++j)
operator()(i, j) = Dn(i, j, n);
calcMaxValue();
}
private:
int Dn(int i, int j, int n) const {
ASSERT(i >= 0 && i < n);
ASSERT(j >= 0 && j < n);
if (n == 2)
return D2[i*2 + j];
else
return
+ 4*Dn(i%(n/2), j%(n/2), n/2)
+ Dn(i/(n/2), j/(n/2), 2);
}
};
class DitheringAlgorithmBase {
public:
virtual ~DitheringAlgorithmBase() { }