Add amount parameter to Error Diffusion algorithm

This commit is contained in:
David Capello 2019-04-03 18:42:10 -03:00
parent e1437a20c3
commit fcf272bb69
19 changed files with 89 additions and 26 deletions

View File

@ -237,6 +237,7 @@
<section id="quantization"> <section id="quantization">
<option id="with_alpha" type="bool" default="true" /> <option id="with_alpha" type="bool" default="true" />
<option id="dithering_algorithm" type="std::string" /> <option id="dithering_algorithm" type="std::string" />
<option id="dithering_factor" type="int" default="100" />
</section> </section>
<section id="eyedropper" text="Editor"> <section id="eyedropper" text="Editor">
<option id="channel" type="EyedropperChannel" default="EyedropperChannel::COLOR_ALPHA" /> <option id="channel" type="EyedropperChannel" default="EyedropperChannel::COLOR_ALPHA" />

View File

@ -482,6 +482,7 @@ delete = &Delete
[color_mode] [color_mode]
title = Color Mode title = Color Mode
amount = Amount:
flatten = Merge layers flatten = Merge layers
[convolution_matrix] [convolution_matrix]

View File

@ -1,5 +1,6 @@
<!-- Aseprite --> <!-- Aseprite -->
<!-- Copyright (C) 2017 by David Capello --> <!-- Copyright (C) 2019 Igara Studio S.A. -->
<!-- Copyright (C) 2017 David Capello -->
<gui> <gui>
<window id="color_mode" text="@.title"> <window id="color_mode" text="@.title">
<vbox> <vbox>
@ -8,6 +9,11 @@
</view> </view>
<slider min="0" max="100" id="progress" minwidth="100" /> <slider min="0" max="100" id="progress" minwidth="100" />
<hbox id="dithering_placeholder" /> <hbox id="dithering_placeholder" />
<hbox id="amount">
<label text="@.amount" />
<slider min="0" max="100" id="factor" minwidth="100" />
<label text="%" />
</hbox>
<check text="@.flatten" id="flatten" /> <check text="@.flatten" id="flatten" />
<separator horizontal="true" /> <separator horizontal="true" />
<hbox> <hbox>

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -69,6 +70,7 @@ SetPixelFormat::SetPixelFormat(Sprite* sprite,
const PixelFormat newFormat, const PixelFormat newFormat,
const render::DitheringAlgorithm ditheringAlgorithm, const render::DitheringAlgorithm ditheringAlgorithm,
const render::DitheringMatrix& ditheringMatrix, const render::DitheringMatrix& ditheringMatrix,
const double ditheringFactor,
render::TaskDelegate* delegate) render::TaskDelegate* delegate)
: WithSprite(sprite) : WithSprite(sprite)
, m_oldFormat(sprite->pixelFormat()) , m_oldFormat(sprite->pixelFormat())
@ -86,6 +88,7 @@ SetPixelFormat::SetPixelFormat(Sprite* sprite,
(old_image.get(), nullptr, newFormat, (old_image.get(), nullptr, newFormat,
ditheringAlgorithm, ditheringAlgorithm,
ditheringMatrix, ditheringMatrix,
ditheringFactor,
sprite->rgbMap(cel->frame()), sprite->rgbMap(cel->frame()),
sprite->palette(cel->frame()), sprite->palette(cel->frame()),
cel->layer()->isBackground(), cel->layer()->isBackground(),

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -32,6 +33,7 @@ namespace cmd {
const doc::PixelFormat newFormat, const doc::PixelFormat newFormat,
const render::DitheringAlgorithm ditheringAlgorithm, const render::DitheringAlgorithm ditheringAlgorithm,
const render::DitheringMatrix& ditheringMatrix, const render::DitheringMatrix& ditheringMatrix,
const double ditheringFactor,
render::TaskDelegate* delegate); render::TaskDelegate* delegate);
protected: protected:

View File

@ -79,6 +79,7 @@ public:
const doc::PixelFormat pixelFormat, const doc::PixelFormat pixelFormat,
const render::DitheringAlgorithm ditheringAlgorithm, const render::DitheringAlgorithm ditheringAlgorithm,
const render::DitheringMatrix& ditheringMatrix, const render::DitheringMatrix& ditheringMatrix,
const double ditheringFactor,
const gfx::Point& pos, const gfx::Point& pos,
const bool newBlend) const bool newBlend)
: m_image(dstImage) : m_image(dstImage)
@ -92,11 +93,13 @@ public:
pixelFormat, pixelFormat,
ditheringAlgorithm, ditheringAlgorithm,
ditheringMatrix, ditheringMatrix,
ditheringFactor,
newBlend]() { // Copy the matrix newBlend]() { // Copy the matrix
run(sprite, frame, run(sprite, frame,
pixelFormat, pixelFormat,
ditheringAlgorithm, ditheringAlgorithm,
ditheringMatrix, ditheringMatrix,
ditheringFactor,
newBlend); newBlend);
}) })
{ {
@ -121,6 +124,7 @@ private:
const doc::PixelFormat pixelFormat, const doc::PixelFormat pixelFormat,
const render::DitheringAlgorithm ditheringAlgorithm, const render::DitheringAlgorithm ditheringAlgorithm,
const render::DitheringMatrix& ditheringMatrix, const render::DitheringMatrix& ditheringMatrix,
const double ditheringFactor,
const bool newBlend) { const bool newBlend) {
doc::ImageRef tmp( doc::ImageRef tmp(
Image::create(sprite->pixelFormat(), Image::create(sprite->pixelFormat(),
@ -142,6 +146,7 @@ private:
pixelFormat, pixelFormat,
ditheringAlgorithm, ditheringAlgorithm,
ditheringMatrix, ditheringMatrix,
ditheringFactor,
sprite->rgbMap(frame), sprite->rgbMap(frame),
sprite->palette(frame), sprite->palette(frame),
(sprite->backgroundLayer() != nullptr), (sprite->backgroundLayer() != nullptr),
@ -208,6 +213,11 @@ public:
m_ditheringSelector->Change.connect( m_ditheringSelector->Change.connect(
base::Bind<void>(&ColorModeWindow::onDithering, this)); base::Bind<void>(&ColorModeWindow::onDithering, this));
ditheringPlaceholder()->addChild(m_ditheringSelector); ditheringPlaceholder()->addChild(m_ditheringSelector);
factor()->Change.connect(base::Bind<void>(&ColorModeWindow::onDithering, this));
}
else {
amount()->setVisible(false);
} }
if (from != IMAGE_GRAYSCALE) if (from != IMAGE_GRAYSCALE)
colorMode()->addChild(new ConversionItem(IMAGE_GRAYSCALE)); colorMode()->addChild(new ConversionItem(IMAGE_GRAYSCALE));
@ -221,6 +231,9 @@ public:
progress()->setReadOnly(true); progress()->setReadOnly(true);
// Default dithering factor
factor()->setValue(Preferences::instance().quantization.ditheringFactor());
// Select first option // Select first option
colorMode()->selectIndex(0); colorMode()->selectIndex(0);
} }
@ -244,6 +257,10 @@ public:
render::BayerMatrix(8)); render::BayerMatrix(8));
} }
double ditheringFactor() const {
return double(factor()->getValue()) / 100.0;
}
bool flattenEnabled() const { bool flattenEnabled() const {
return flatten()->isSelected(); return flatten()->isSelected();
} }
@ -252,7 +269,14 @@ public:
void saveDitheringAlgorithm() { void saveDitheringAlgorithm() {
if (m_ditheringSelector) { if (m_ditheringSelector) {
if (auto item = m_ditheringSelector->getSelectedItem()) { if (auto item = m_ditheringSelector->getSelectedItem()) {
Preferences::instance().quantization.ditheringAlgorithm(item->text()); Preferences::instance().quantization.ditheringAlgorithm(
item->text());
if (m_ditheringSelector->ditheringAlgorithm() ==
render::DitheringAlgorithm::ErrorDiffusion) {
Preferences::instance().quantization.ditheringFactor(
factor()->getValue());
}
} }
} }
} }
@ -285,8 +309,15 @@ private:
doc::PixelFormat dstPixelFormat = item->pixelFormat(); doc::PixelFormat dstPixelFormat = item->pixelFormat();
if (m_ditheringSelector) if (m_ditheringSelector) {
m_ditheringSelector->setVisible(dstPixelFormat == doc::IMAGE_INDEXED); const bool toIndexed = (dstPixelFormat == doc::IMAGE_INDEXED);
m_ditheringSelector->setVisible(toIndexed);
const bool errorDiff =
(m_ditheringSelector->ditheringAlgorithm() ==
render::DitheringAlgorithm::ErrorDiffusion);
amount()->setVisible(toIndexed && errorDiff);
}
m_image.reset( m_image.reset(
Image::create(dstPixelFormat, Image::create(dstPixelFormat,
@ -315,6 +346,7 @@ private:
dstPixelFormat, dstPixelFormat,
ditheringAlgorithm(), ditheringAlgorithm(),
ditheringMatrix(), ditheringMatrix(),
ditheringFactor(),
visibleBounds.origin(), visibleBounds.origin(),
Preferences::instance().experimental.newBlend())); Preferences::instance().experimental.newBlend()));
@ -373,6 +405,7 @@ private:
doc::PixelFormat m_format; doc::PixelFormat m_format;
render::DitheringAlgorithm m_ditheringAlgorithm; render::DitheringAlgorithm m_ditheringAlgorithm;
render::DitheringMatrix m_ditheringMatrix; render::DitheringMatrix m_ditheringMatrix;
double m_ditheringFactor;
}; };
ChangePixelFormatCommand::ChangePixelFormatCommand() ChangePixelFormatCommand::ChangePixelFormatCommand()
@ -381,6 +414,7 @@ ChangePixelFormatCommand::ChangePixelFormatCommand()
m_useUI = true; m_useUI = true;
m_format = IMAGE_RGB; m_format = IMAGE_RGB;
m_ditheringAlgorithm = render::DitheringAlgorithm::None; m_ditheringAlgorithm = render::DitheringAlgorithm::None;
m_ditheringFactor = 1.0;
} }
void ChangePixelFormatCommand::onLoadParams(const Params& params) void ChangePixelFormatCommand::onLoadParams(const Params& params)
@ -483,6 +517,7 @@ void ChangePixelFormatCommand::onExecute(Context* context)
m_format = window.pixelFormat(); m_format = window.pixelFormat();
m_ditheringAlgorithm = window.ditheringAlgorithm(); m_ditheringAlgorithm = window.ditheringAlgorithm();
m_ditheringMatrix = window.ditheringMatrix(); m_ditheringMatrix = window.ditheringMatrix();
m_ditheringFactor = window.ditheringFactor();
flatten = window.flattenEnabled(); flatten = window.flattenEnabled();
window.saveDitheringAlgorithm(); window.saveDitheringAlgorithm();
@ -520,6 +555,7 @@ void ChangePixelFormatCommand::onExecute(Context* context)
sprite, m_format, sprite, m_format,
m_ditheringAlgorithm, m_ditheringAlgorithm,
m_ditheringMatrix, m_ditheringMatrix,
m_ditheringFactor,
&job)); // SpriteJob is a render::TaskDelegate &job)); // SpriteJob is a render::TaskDelegate
}); });
job.waitJob(); job.waitJob();

View File

@ -283,7 +283,7 @@ void NewLayerCommand::onExecute(Context* context)
nullptr, nullptr,
sprite->pixelFormat(), sprite->pixelFormat(),
render::DitheringAlgorithm::None, render::DitheringAlgorithm::None,
render::DitheringMatrix(), render::DitheringMatrix(), 1.0,
sprite->rgbMap(dstFrame), sprite->rgbMap(dstFrame),
pasteSpr->palette(fr), pasteSpr->palette(fr),
(pasteSpr->backgroundLayer() ? true: false), (pasteSpr->backgroundLayer() ? true: false),

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -184,7 +185,7 @@ void PasteTextCommand::onExecute(Context* ctx)
render::convert_pixel_format( render::convert_pixel_format(
image.get(), NULL, sprite->pixelFormat(), image.get(), NULL, sprite->pixelFormat(),
render::DitheringAlgorithm::None, render::DitheringAlgorithm::None,
render::DitheringMatrix(), render::DitheringMatrix(), 1.0,
rgbmap, sprite->palette(editor->frame()), rgbmap, sprite->palette(editor->frame()),
false, 0)); false, 0));
} }

View File

@ -744,7 +744,7 @@ void DocExporter::renderTexture(Context* ctx, const Samples& samples, Image* tex
sample.sprite(), sample.sprite(),
textureImage->pixelFormat(), textureImage->pixelFormat(),
render::DitheringAlgorithm::None, render::DitheringAlgorithm::None,
render::DitheringMatrix(), render::DitheringMatrix(), 1.0,
nullptr) // TODO add a delegate to show progress nullptr) // TODO add a delegate to show progress
.execute(ctx); .execute(ctx);
} }

View File

@ -746,7 +746,7 @@ private:
render::convert_pixel_format render::convert_pixel_format
(oldImage, NULL, IMAGE_RGB, (oldImage, NULL, IMAGE_RGB,
render::DitheringAlgorithm::None, render::DitheringAlgorithm::None,
render::DitheringMatrix(), render::DitheringMatrix(), 1.0,
nullptr, nullptr,
m_sprite->palette(cel->frame()), m_sprite->palette(cel->frame()),
m_opaque, m_opaque,
@ -759,7 +759,7 @@ private:
render::convert_pixel_format render::convert_pixel_format
(m_currentImage.get(), NULL, IMAGE_RGB, (m_currentImage.get(), NULL, IMAGE_RGB,
render::DitheringAlgorithm::None, render::DitheringAlgorithm::None,
render::DitheringMatrix(), render::DitheringMatrix(), 1.0,
nullptr, nullptr,
m_sprite->palette(m_frameNum), m_sprite->palette(m_frameNum),
m_opaque, m_opaque,
@ -769,7 +769,7 @@ private:
render::convert_pixel_format render::convert_pixel_format
(m_previousImage.get(), NULL, IMAGE_RGB, (m_previousImage.get(), NULL, IMAGE_RGB,
render::DitheringAlgorithm::None, render::DitheringAlgorithm::None,
render::DitheringMatrix(), render::DitheringMatrix(), 1.0,
nullptr, nullptr,
m_sprite->palette(MAX(0, m_frameNum-1)), m_sprite->palette(MAX(0, m_frameNum-1)),
m_opaque, m_opaque,

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2017 David Capello // Copyright (C) 2017 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -101,7 +102,8 @@ private:
doc::clear_image(image2.get(), 0); doc::clear_image(image2.get(), 0);
render::convert_pixel_format( render::convert_pixel_format(
image1.get(), image2.get(), IMAGE_INDEXED, image1.get(), image2.get(), IMAGE_INDEXED,
m_algo, m_matrix, nullptr, palette, true, -1, nullptr); m_algo, m_matrix, 1.0,
nullptr, palette, true, -1, nullptr);
} }
m_preview = os::instance()->createRgbaSurface(w, h); m_preview = os::instance()->createRgbaSurface(w, h);

View File

@ -367,7 +367,7 @@ void paste()
render::convert_pixel_format( render::convert_pixel_format(
clipboard_image.get(), NULL, dstSpr->pixelFormat(), clipboard_image.get(), NULL, dstSpr->pixelFormat(),
render::DitheringAlgorithm::None, render::DitheringAlgorithm::None,
render::DitheringMatrix(), render::DitheringMatrix(), 1.0,
dst_rgbmap, clipboard_palette.get(), dst_rgbmap, clipboard_palette.get(),
false, false,
0)); 0));

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -54,7 +55,7 @@ Cel* create_cel_copy(const Cel* srcCel,
tmpImage.get(), tmpImage.get(),
IMAGE_RGB, IMAGE_RGB,
render::DitheringAlgorithm::None, render::DitheringAlgorithm::None,
render::DitheringMatrix(), render::DitheringMatrix(), 1.0,
srcCel->sprite()->rgbMap(srcCel->frame()), srcCel->sprite()->rgbMap(srcCel->frame()),
srcCel->sprite()->palette(srcCel->frame()), srcCel->sprite()->palette(srcCel->frame()),
srcCel->layer()->isBackground(), srcCel->layer()->isBackground(),
@ -65,7 +66,7 @@ Cel* create_cel_copy(const Cel* srcCel,
dstCel->image(), dstCel->image(),
IMAGE_INDEXED, IMAGE_INDEXED,
render::DitheringAlgorithm::None, render::DitheringAlgorithm::None,
render::DitheringMatrix(), render::DitheringMatrix(), 1.0,
dstSprite->rgbMap(dstFrame), dstSprite->rgbMap(dstFrame),
dstSprite->palette(dstFrame), dstSprite->palette(dstFrame),
srcCel->layer()->isBackground(), srcCel->layer()->isBackground(),

View File

@ -25,13 +25,15 @@ ErrorDiffusionDither::ErrorDiffusionDither(int transparentIndex)
void ErrorDiffusionDither::start( void ErrorDiffusionDither::start(
const doc::Image* srcImage, const doc::Image* srcImage,
doc::Image* dstImage) doc::Image* dstImage,
const double factor)
{ {
m_srcImage = srcImage; m_srcImage = srcImage;
m_width = 2+srcImage->width(); m_width = 2+srcImage->width();
for (int i=0; i<kChannels; ++i) for (int i=0; i<kChannels; ++i)
m_err[i].resize(m_width*2, 0); m_err[i].resize(m_width*2, 0);
m_lastY = -1; m_lastY = -1;
m_factor = int(factor * 100.0);
} }
void ErrorDiffusionDither::finish() void ErrorDiffusionDither::finish()
@ -89,11 +91,11 @@ doc::color_t ErrorDiffusionDither::ditherRgbToIndex2D(
// TODO using Floyd-Steinberg matrix here but it should be configurable // TODO using Floyd-Steinberg matrix here but it should be configurable
for (int i=0; i<kChannels; ++i) { for (int i=0; i<kChannels; ++i) {
int* err = &m_err[i][x]; int* err = &m_err[i][x];
const int q = quantError[i] * m_factor / 100;
const int a = quantError[i] * 7 / 16; const int a = q * 7 / 16;
const int b = quantError[i] * 3 / 16; const int b = q * 3 / 16;
const int c = quantError[i] * 5 / 16; const int c = q * 5 / 16;
const int d = quantError[i] * 1 / 16; const int d = q * 1 / 16;
if (y & 1) { if (y & 1) {
err[0 ] += a; err[0 ] += a;

View File

@ -23,7 +23,8 @@ namespace render {
bool zigZag() const override { return true; } bool zigZag() const override { return true; }
void start( void start(
const doc::Image* srcImage, const doc::Image* srcImage,
doc::Image* dstImage) override; doc::Image* dstImage,
const double factor) override;
void finish() override; void finish() override;
doc::color_t ditherRgbToIndex2D( doc::color_t ditherRgbToIndex2D(
const int x, const int y, const int x, const int y,
@ -35,6 +36,7 @@ namespace render {
int m_width, m_lastY; int m_width, m_lastY;
static const int kChannels = 4; static const int kChannels = 4;
std::vector<int> m_err[kChannels]; std::vector<int> m_err[kChannels];
int m_factor;
}; };
} // namespace render } // namespace render

View File

@ -229,6 +229,7 @@ doc::color_t OrderedDither2::ditherRgbPixelToIndex(
void dither_rgb_image_to_indexed( void dither_rgb_image_to_indexed(
DitheringAlgorithmBase& algorithm, DitheringAlgorithmBase& algorithm,
const DitheringMatrix& matrix, const DitheringMatrix& matrix,
const double factor,
const doc::Image* srcImage, const doc::Image* srcImage,
doc::Image* dstImage, doc::Image* dstImage,
const doc::RgbMap* rgbmap, const doc::RgbMap* rgbmap,
@ -238,7 +239,7 @@ void dither_rgb_image_to_indexed(
const int w = srcImage->width(); const int w = srcImage->width();
const int h = srcImage->height(); const int h = srcImage->height();
algorithm.start(srcImage, dstImage); algorithm.start(srcImage, dstImage, factor);
if (algorithm.dimensions() == 1) { if (algorithm.dimensions() == 1) {
const doc::LockImageBits<doc::RgbTraits> srcBits(srcImage); const doc::LockImageBits<doc::RgbTraits> srcBits(srcImage);

View File

@ -29,7 +29,8 @@ namespace render {
virtual void start( virtual void start(
const doc::Image* srcImage, const doc::Image* srcImage,
doc::Image* dstImage) { } doc::Image* dstImage,
const double factor) { }
virtual void finish() { } virtual void finish() { }
@ -77,6 +78,7 @@ namespace render {
void dither_rgb_image_to_indexed( void dither_rgb_image_to_indexed(
DitheringAlgorithmBase& algorithm, DitheringAlgorithmBase& algorithm,
const DitheringMatrix& matrix, const DitheringMatrix& matrix,
const double factor,
const doc::Image* srcImage, const doc::Image* srcImage,
doc::Image* dstImage, doc::Image* dstImage,
const doc::RgbMap* rgbmap, const doc::RgbMap* rgbmap,

View File

@ -86,6 +86,7 @@ Image* convert_pixel_format(
PixelFormat pixelFormat, PixelFormat pixelFormat,
DitheringAlgorithm ditheringAlgorithm, DitheringAlgorithm ditheringAlgorithm,
const DitheringMatrix& ditheringMatrix, const DitheringMatrix& ditheringMatrix,
const double ditheringFactor,
const RgbMap* rgbmap, const RgbMap* rgbmap,
const Palette* palette, const Palette* palette,
bool is_background, bool is_background,
@ -114,7 +115,8 @@ Image* convert_pixel_format(
} }
if (dither) if (dither)
dither_rgb_image_to_indexed( dither_rgb_image_to_indexed(
*dither, ditheringMatrix, image, new_image, rgbmap, palette, delegate); *dither, ditheringMatrix, ditheringFactor,
image, new_image, rgbmap, palette, delegate);
return new_image; return new_image;
} }

View File

@ -55,6 +55,7 @@ namespace render {
doc::PixelFormat pixelFormat, doc::PixelFormat pixelFormat,
render::DitheringAlgorithm ditheringAlgorithm, render::DitheringAlgorithm ditheringAlgorithm,
const render::DitheringMatrix& ditheringMatrix, const render::DitheringMatrix& ditheringMatrix,
const double ditheringFactor,
const doc::RgbMap* rgbmap, const doc::RgbMap* rgbmap,
const doc::Palette* palette, const doc::Palette* palette,
bool is_background, bool is_background,