mirror of
https://github.com/aseprite/aseprite.git
synced 2025-01-02 11:59:58 +00:00
Add dithering options for Gradient tool (#418)
This commit is contained in:
parent
adbcc75fad
commit
ae67cab015
@ -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()).
|
||||
|
@ -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
|
||||
|
@ -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));
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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()
|
||||
|
@ -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
15
src/render/dithering.h
Normal 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
|
82
src/render/dithering_matrix.h
Normal file
82
src/render/dithering_matrix.h
Normal 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
|
@ -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() { }
|
||||
|
Loading…
Reference in New Issue
Block a user