mirror of https://github.com/aseprite/aseprite.git
Merge bd2ae1eb61
into 2ba051b59b
This commit is contained in:
commit
e273a35f04
|
@ -329,6 +329,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="zig_zag" type="bool" default="true" />
|
||||||
<option id="dithering_factor" type="int" default="100" />
|
<option id="dithering_factor" type="int" default="100" />
|
||||||
<option id="to_gray" type="ToGrayAlgorithm" default="ToGrayAlgorithm::DEFAULT" />
|
<option id="to_gray" type="ToGrayAlgorithm" default="ToGrayAlgorithm::DEFAULT" />
|
||||||
<option id="advanced" type="bool" default="false" />
|
<option id="advanced" type="bool" default="false" />
|
||||||
|
|
|
@ -235,8 +235,14 @@ ChangePixelFormat_Grayscale = Grayscale
|
||||||
ChangePixelFormat_Indexed = Indexed
|
ChangePixelFormat_Indexed = Indexed
|
||||||
ChangePixelFormat_Indexed_OrderedDithering = Indexed with Ordered Dithering
|
ChangePixelFormat_Indexed_OrderedDithering = Indexed with Ordered Dithering
|
||||||
ChangePixelFormat_Indexed_OldDithering = Indexed with Old Dithering
|
ChangePixelFormat_Indexed_OldDithering = Indexed with Old Dithering
|
||||||
ChangePixelFormat_Indexed_ErrorDiffusion = Indexed with Floyd-Steinberg Error Diffusion Dithering
|
ChangePixelFormat_Indexed_FloydSteinberg = Indexed with Floyd-Steinberg Error Diffusion Dithering
|
||||||
|
ChangePixelFormat_Indexed_JarvisJudiceNinke = Indexed with Jarvis-Judice-Ninke Error Diffusion Dithering
|
||||||
|
ChangePixelFormat_Indexed_Stucki = Indexed with Stucki Error Diffusion Dithering
|
||||||
|
ChangePixelFormat_Indexed_Atkinson = Indexed with Atkinson Error Diffusion Dithering
|
||||||
|
ChangePixelFormat_Indexed_Burkes = Indexed with Burkes Error Diffusion Dithering
|
||||||
|
ChangePixelFormat_Indexed_Sierra = Indexed with Sierra Error Diffusion Dithering
|
||||||
ChangePixelFormat_MoreOptions = More Options
|
ChangePixelFormat_MoreOptions = More Options
|
||||||
|
ChangePixelFormat_ZigZag = Zig Zag
|
||||||
Clear = Clear
|
Clear = Clear
|
||||||
ClearCel = Clear Cel
|
ClearCel = Clear Cel
|
||||||
ClearRecentFiles = Clear Recent Files
|
ClearRecentFiles = Clear Recent Files
|
||||||
|
@ -516,6 +522,11 @@ no_dithering = No Dithering
|
||||||
old_dithering = Old Dithering +\s
|
old_dithering = Old Dithering +\s
|
||||||
ordered_dithering = Ordered Dithering +\s
|
ordered_dithering = Ordered Dithering +\s
|
||||||
floyd_steinberg = Floyd-Steinberg Error Diffusion Dithering
|
floyd_steinberg = Floyd-Steinberg Error Diffusion Dithering
|
||||||
|
jarvis_judice_ninke = Jarvis Judice Ninke Error Diffusion Dithering
|
||||||
|
stucki = Stucki Error Diffusion Dithering
|
||||||
|
atkinson = Atkinson Error Diffusion Dithering
|
||||||
|
burkes = Burkes Error Diffusion Dithering
|
||||||
|
sierra = Sierra Error Diffusion Dithering
|
||||||
|
|
||||||
[canvas_size]
|
[canvas_size]
|
||||||
title = Canvas Size
|
title = Canvas Size
|
||||||
|
|
|
@ -447,19 +447,13 @@ int CliProcessor::process(Context* ctx)
|
||||||
}
|
}
|
||||||
// --dithering-algorithm <algorithm>
|
// --dithering-algorithm <algorithm>
|
||||||
else if (opt == &m_options.ditheringAlgorithm()) {
|
else if (opt == &m_options.ditheringAlgorithm()) {
|
||||||
if (value.value() == "none")
|
ditheringAlgorithm = render::DitheringAlgorithmFromString(value.value());
|
||||||
ditheringAlgorithm = render::DitheringAlgorithm::None;
|
if (ditheringAlgorithm == render::DitheringAlgorithm::Unknown) {
|
||||||
else if (value.value() == "ordered")
|
|
||||||
ditheringAlgorithm = render::DitheringAlgorithm::Ordered;
|
|
||||||
else if (value.value() == "old")
|
|
||||||
ditheringAlgorithm = render::DitheringAlgorithm::Old;
|
|
||||||
else if (value.value() == "error-diffusion")
|
|
||||||
ditheringAlgorithm = render::DitheringAlgorithm::ErrorDiffusion;
|
|
||||||
else
|
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
"--dithering-algorithm needs a valid algorithm name\n"
|
"--dithering-algorithm needs a valid algorithm name\n"
|
||||||
"Usage: --dithering-algorithm <algorithm>\n"
|
"Usage: --dithering-algorithm <algorithm>\n"
|
||||||
"Where <algorithm> can be none, ordered, old, or error-diffusion");
|
"Where <algorithm> can be: none, ordered, old, floyd-steinberg, jarvis-judice-ninke, stucki, atkinson, burkes or sierra.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// --dithering-matrix <id>
|
// --dithering-matrix <id>
|
||||||
else if (opt == &m_options.ditheringMatrix()) {
|
else if (opt == &m_options.ditheringMatrix()) {
|
||||||
|
@ -477,15 +471,7 @@ int CliProcessor::process(Context* ctx)
|
||||||
}
|
}
|
||||||
else if (value.value() == "indexed") {
|
else if (value.value() == "indexed") {
|
||||||
params.set("format", "indexed");
|
params.set("format", "indexed");
|
||||||
switch (ditheringAlgorithm) {
|
params.set("dithering", DitheringAlgorithmToString(ditheringAlgorithm).c_str());
|
||||||
case render::DitheringAlgorithm::None: params.set("dithering", "none"); break;
|
|
||||||
case render::DitheringAlgorithm::Ordered: params.set("dithering", "ordered"); break;
|
|
||||||
case render::DitheringAlgorithm::Old: params.set("dithering", "old"); break;
|
|
||||||
case render::DitheringAlgorithm::ErrorDiffusion:
|
|
||||||
params.set("dithering", "error-diffusion");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ditheringAlgorithm != render::DitheringAlgorithm::None &&
|
if (ditheringAlgorithm != render::DitheringAlgorithm::None &&
|
||||||
!ditheringMatrix.empty()) {
|
!ditheringMatrix.empty()) {
|
||||||
params.set("dithering-matrix", ditheringMatrix.c_str());
|
params.set("dithering-matrix", ditheringMatrix.c_str());
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
#include "render/quantization.h"
|
#include "render/quantization.h"
|
||||||
#include "render/render.h"
|
#include "render/render.h"
|
||||||
#include "render/task_delegate.h"
|
#include "render/task_delegate.h"
|
||||||
|
#include "ui/button.h"
|
||||||
#include "ui/listitem.h"
|
#include "ui/listitem.h"
|
||||||
#include "ui/paint_event.h"
|
#include "ui/paint_event.h"
|
||||||
#include "ui/size_hint_event.h"
|
#include "ui/size_hint_event.h"
|
||||||
|
@ -177,6 +178,7 @@ public:
|
||||||
, m_imageBuffer(new doc::ImageBuffer)
|
, m_imageBuffer(new doc::ImageBuffer)
|
||||||
, m_selectedItem(nullptr)
|
, m_selectedItem(nullptr)
|
||||||
, m_ditheringSelector(nullptr)
|
, m_ditheringSelector(nullptr)
|
||||||
|
, m_zigZagCheck(nullptr)
|
||||||
, m_mapAlgorithmSelector(nullptr)
|
, m_mapAlgorithmSelector(nullptr)
|
||||||
, m_bestFitCriteriaSelector(nullptr)
|
, m_bestFitCriteriaSelector(nullptr)
|
||||||
, m_imageJustCreated(true)
|
, m_imageJustCreated(true)
|
||||||
|
@ -204,6 +206,8 @@ public:
|
||||||
m_ditheringSelector = new DitheringSelector(DitheringSelector::SelectBoth);
|
m_ditheringSelector = new DitheringSelector(DitheringSelector::SelectBoth);
|
||||||
m_ditheringSelector->setExpansive(true);
|
m_ditheringSelector->setExpansive(true);
|
||||||
|
|
||||||
|
m_zigZagCheck = new CheckBox(Strings::commands_ChangePixelFormat_ZigZag());
|
||||||
|
|
||||||
m_mapAlgorithmSelector = new RgbMapAlgorithmSelector;
|
m_mapAlgorithmSelector = new RgbMapAlgorithmSelector;
|
||||||
m_mapAlgorithmSelector->setExpansive(true);
|
m_mapAlgorithmSelector->setExpansive(true);
|
||||||
|
|
||||||
|
@ -217,6 +221,9 @@ public:
|
||||||
m_ditheringSelector->setSelectedItemIndex(index);
|
m_ditheringSelector->setSelectedItemIndex(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Select default zig zag
|
||||||
|
m_zigZagCheck->setSelected(pref.quantization.zigZag());
|
||||||
|
|
||||||
// Select default RgbMap algorithm
|
// Select default RgbMap algorithm
|
||||||
m_mapAlgorithmSelector->algorithm(pref.quantization.rgbmapAlgorithm());
|
m_mapAlgorithmSelector->algorithm(pref.quantization.rgbmapAlgorithm());
|
||||||
|
|
||||||
|
@ -224,6 +231,7 @@ public:
|
||||||
m_bestFitCriteriaSelector->criteria(pref.quantization.fitCriteria());
|
m_bestFitCriteriaSelector->criteria(pref.quantization.fitCriteria());
|
||||||
|
|
||||||
ditheringPlaceholder()->addChild(m_ditheringSelector);
|
ditheringPlaceholder()->addChild(m_ditheringSelector);
|
||||||
|
ditheringPlaceholder()->addChild(m_zigZagCheck);
|
||||||
rgbmapAlgorithmPlaceholder()->addChild(m_mapAlgorithmSelector);
|
rgbmapAlgorithmPlaceholder()->addChild(m_mapAlgorithmSelector);
|
||||||
bestFitCriteriaPlaceholder()->addChild(m_bestFitCriteriaSelector);
|
bestFitCriteriaPlaceholder()->addChild(m_bestFitCriteriaSelector);
|
||||||
|
|
||||||
|
@ -233,6 +241,7 @@ public:
|
||||||
|
|
||||||
// Signals
|
// Signals
|
||||||
m_ditheringSelector->Change.connect([this] { onIndexParamChange(); });
|
m_ditheringSelector->Change.connect([this] { onIndexParamChange(); });
|
||||||
|
m_zigZagCheck->Click.connect([this] { onIndexParamChange(); });
|
||||||
m_mapAlgorithmSelector->Change.connect([this] { onIndexParamChange(); });
|
m_mapAlgorithmSelector->Change.connect([this] { onIndexParamChange(); });
|
||||||
m_bestFitCriteriaSelector->Change.connect([this] { onIndexParamChange(); });
|
m_bestFitCriteriaSelector->Change.connect([this] { onIndexParamChange(); });
|
||||||
factor()->Change.connect([this] { onIndexParamChange(); });
|
factor()->Change.connect([this] { onIndexParamChange(); });
|
||||||
|
@ -281,6 +290,7 @@ public:
|
||||||
if (m_ditheringSelector) {
|
if (m_ditheringSelector) {
|
||||||
d.algorithm(m_ditheringSelector->ditheringAlgorithm());
|
d.algorithm(m_ditheringSelector->ditheringAlgorithm());
|
||||||
d.matrix(m_ditheringSelector->ditheringMatrix());
|
d.matrix(m_ditheringSelector->ditheringMatrix());
|
||||||
|
d.zigzag(m_zigZagCheck->isSelected());
|
||||||
}
|
}
|
||||||
d.factor(double(factor()->getValue()) / 100.0);
|
d.factor(double(factor()->getValue()) / 100.0);
|
||||||
return d;
|
return d;
|
||||||
|
@ -317,16 +327,16 @@ public:
|
||||||
{
|
{
|
||||||
auto& pref = Preferences::instance();
|
auto& pref = Preferences::instance();
|
||||||
|
|
||||||
// Save the dithering method used for the future
|
// Save the dithering method and zig-zag setting used for the future
|
||||||
if (m_ditheringSelector) {
|
if (m_ditheringSelector) {
|
||||||
if (auto item = m_ditheringSelector->getSelectedItem()) {
|
if (auto item = m_ditheringSelector->getSelectedItem()) {
|
||||||
pref.quantization.ditheringAlgorithm(item->text());
|
pref.quantization.ditheringAlgorithm(item->text());
|
||||||
|
|
||||||
if (m_ditheringSelector->ditheringAlgorithm() ==
|
if (DitheringAlgorithmIsDiffusion(m_ditheringSelector->ditheringAlgorithm()))
|
||||||
render::DitheringAlgorithm::ErrorDiffusion) {
|
|
||||||
pref.quantization.ditheringFactor(factor()->getValue());
|
pref.quantization.ditheringFactor(factor()->getValue());
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pref.quantization.zigZag(m_zigZagCheck->isEnabled());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_mapAlgorithmSelector || m_bestFitCriteriaSelector)
|
if (m_mapAlgorithmSelector || m_bestFitCriteriaSelector)
|
||||||
|
@ -364,9 +374,10 @@ private:
|
||||||
if (m_ditheringSelector) {
|
if (m_ditheringSelector) {
|
||||||
const bool toIndexed = (dstColorMode == doc::ColorMode::INDEXED);
|
const bool toIndexed = (dstColorMode == doc::ColorMode::INDEXED);
|
||||||
m_ditheringSelector->setVisible(toIndexed);
|
m_ditheringSelector->setVisible(toIndexed);
|
||||||
|
m_zigZagCheck->setVisible(toIndexed);
|
||||||
|
|
||||||
const bool errorDiff = (m_ditheringSelector->ditheringAlgorithm() ==
|
const bool errorDiff =
|
||||||
render::DitheringAlgorithm::ErrorDiffusion);
|
(render::DitheringAlgorithmIsDiffusion(m_ditheringSelector->ditheringAlgorithm()));
|
||||||
amount()->setVisible(toIndexed && errorDiff);
|
amount()->setVisible(toIndexed && errorDiff);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -460,6 +471,7 @@ private:
|
||||||
std::unique_ptr<ConvertThread> m_bgThread;
|
std::unique_ptr<ConvertThread> m_bgThread;
|
||||||
ConversionItem* m_selectedItem;
|
ConversionItem* m_selectedItem;
|
||||||
DitheringSelector* m_ditheringSelector;
|
DitheringSelector* m_ditheringSelector;
|
||||||
|
CheckBox* m_zigZagCheck;
|
||||||
RgbMapAlgorithmSelector* m_mapAlgorithmSelector;
|
RgbMapAlgorithmSelector* m_mapAlgorithmSelector;
|
||||||
BestFitCriteriaSelector* m_bestFitCriteriaSelector;
|
BestFitCriteriaSelector* m_bestFitCriteriaSelector;
|
||||||
bool m_imageJustCreated;
|
bool m_imageJustCreated;
|
||||||
|
@ -487,6 +499,11 @@ struct ChangePixelFormatParams : public NewParams {
|
||||||
1.0,
|
1.0,
|
||||||
{ "ditheringFactor", "dithering-factor" }
|
{ "ditheringFactor", "dithering-factor" }
|
||||||
};
|
};
|
||||||
|
Param<bool> zigZag{
|
||||||
|
this,
|
||||||
|
true,
|
||||||
|
{ "zigZag", "zig-zag" }
|
||||||
|
};
|
||||||
Param<doc::RgbMapAlgorithm> rgbmap{ this, RgbMapAlgorithm::DEFAULT, "rgbmap" };
|
Param<doc::RgbMapAlgorithm> rgbmap{ this, RgbMapAlgorithm::DEFAULT, "rgbmap" };
|
||||||
Param<gen::ToGrayAlgorithm> toGray{ this, gen::ToGrayAlgorithm::DEFAULT, "toGray" };
|
Param<gen::ToGrayAlgorithm> toGray{ this, gen::ToGrayAlgorithm::DEFAULT, "toGray" };
|
||||||
Param<doc::FitCriteria> fitCriteria{ this, doc::FitCriteria::DEFAULT, "fitCriteria" };
|
Param<doc::FitCriteria> fitCriteria{ this, doc::FitCriteria::DEFAULT, "fitCriteria" };
|
||||||
|
@ -568,10 +585,13 @@ void ChangePixelFormatCommand::onExecute(Context* ctx)
|
||||||
if (window.closer() != window.ok())
|
if (window.closer() != window.ok())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
const auto d = window.dithering();
|
||||||
|
|
||||||
params().colorMode(window.selectedColorMode());
|
params().colorMode(window.selectedColorMode());
|
||||||
params().dithering(window.dithering().algorithm());
|
params().dithering(d.algorithm());
|
||||||
matrix = window.dithering().matrix();
|
matrix = d.matrix();
|
||||||
params().factor(window.dithering().factor());
|
params().factor(d.factor());
|
||||||
|
params().zigZag(d.zigzag());
|
||||||
params().rgbmap(window.rgbMapAlgorithm());
|
params().rgbmap(window.rgbMapAlgorithm());
|
||||||
params().fitCriteria(window.fitCriteria());
|
params().fitCriteria(window.fitCriteria());
|
||||||
params().toGray(window.toGray());
|
params().toGray(window.toGray());
|
||||||
|
@ -615,13 +635,14 @@ void ChangePixelFormatCommand::onExecute(Context* ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
job.startJobWithCallback([this, &job, sprite, &matrix](Tx& tx) {
|
job.startJobWithCallback([this, &job, sprite, &matrix](Tx& tx) {
|
||||||
tx(new cmd::SetPixelFormat(sprite,
|
tx(new cmd::SetPixelFormat(
|
||||||
(PixelFormat)params().colorMode(),
|
sprite,
|
||||||
render::Dithering(params().dithering(), matrix, params().factor()),
|
(PixelFormat)params().colorMode(),
|
||||||
params().rgbmap(),
|
render::Dithering(params().dithering(), matrix, params().zigZag(), params().factor()),
|
||||||
get_gray_func(params().toGray()),
|
params().rgbmap(),
|
||||||
&job, // SpriteJob is a render::TaskDelegate
|
get_gray_func(params().toGray()),
|
||||||
params().fitCriteria()));
|
&job, // SpriteJob is a render::TaskDelegate
|
||||||
|
params().fitCriteria()));
|
||||||
});
|
});
|
||||||
job.waitJob();
|
job.waitJob();
|
||||||
}
|
}
|
||||||
|
@ -651,8 +672,23 @@ std::string ChangePixelFormatCommand::onGetFriendlyName() const
|
||||||
case render::DitheringAlgorithm::Old:
|
case render::DitheringAlgorithm::Old:
|
||||||
conversion = Strings::commands_ChangePixelFormat_Indexed_OldDithering();
|
conversion = Strings::commands_ChangePixelFormat_Indexed_OldDithering();
|
||||||
break;
|
break;
|
||||||
case render::DitheringAlgorithm::ErrorDiffusion:
|
case render::DitheringAlgorithm::FloydSteinberg:
|
||||||
conversion = Strings::commands_ChangePixelFormat_Indexed_ErrorDiffusion();
|
conversion = Strings::commands_ChangePixelFormat_Indexed_FloydSteinberg();
|
||||||
|
break;
|
||||||
|
case render::DitheringAlgorithm::JarvisJudiceNinke:
|
||||||
|
conversion = Strings::commands_ChangePixelFormat_Indexed_JarvisJudiceNinke();
|
||||||
|
break;
|
||||||
|
case render::DitheringAlgorithm::Stucki:
|
||||||
|
conversion = Strings::commands_ChangePixelFormat_Indexed_Stucki();
|
||||||
|
break;
|
||||||
|
case render::DitheringAlgorithm::Atkinson:
|
||||||
|
conversion = Strings::commands_ChangePixelFormat_Indexed_Atkinson();
|
||||||
|
break;
|
||||||
|
case render::DitheringAlgorithm::Burkes:
|
||||||
|
conversion = Strings::commands_ChangePixelFormat_Indexed_Burkes();
|
||||||
|
break;
|
||||||
|
case render::DitheringAlgorithm::Sierra:
|
||||||
|
conversion = Strings::commands_ChangePixelFormat_Indexed_Sierra();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -275,14 +275,10 @@ void Param<doc::FitCriteria>::fromString(const std::string& value)
|
||||||
template<>
|
template<>
|
||||||
void Param<render::DitheringAlgorithm>::fromString(const std::string& value)
|
void Param<render::DitheringAlgorithm>::fromString(const std::string& value)
|
||||||
{
|
{
|
||||||
if (base::utf8_icmp(value, "ordered") == 0)
|
auto algo = render::DitheringAlgorithmFromString(value);
|
||||||
setValue(render::DitheringAlgorithm::Ordered);
|
if (algo == render::DitheringAlgorithm::Unknown)
|
||||||
else if (base::utf8_icmp(value, "old") == 0)
|
algo = render::DitheringAlgorithm::None;
|
||||||
setValue(render::DitheringAlgorithm::Old);
|
setValue(algo);
|
||||||
else if (base::utf8_icmp(value, "error-diffusion") == 0)
|
|
||||||
setValue(render::DitheringAlgorithm::ErrorDiffusion);
|
|
||||||
else
|
|
||||||
setValue(render::DitheringAlgorithm::None);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
|
|
@ -2577,8 +2577,26 @@ render::DitheringAlgorithmBase* ContextBar::ditheringAlgorithm()
|
||||||
case render::DitheringAlgorithm::None: s_dither.reset(nullptr); break;
|
case render::DitheringAlgorithm::None: s_dither.reset(nullptr); break;
|
||||||
case render::DitheringAlgorithm::Ordered: s_dither.reset(new render::OrderedDither2(-1)); 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;
|
case render::DitheringAlgorithm::Old: s_dither.reset(new render::OrderedDither(-1)); break;
|
||||||
case render::DitheringAlgorithm::ErrorDiffusion:
|
case render::DitheringAlgorithm::FloydSteinberg:
|
||||||
s_dither.reset(new render::ErrorDiffusionDither(-1));
|
s_dither.reset(
|
||||||
|
new render::ErrorDiffusionDither(render::ErrorDiffusionType::FloydSteinberg, -1));
|
||||||
|
break;
|
||||||
|
case render::DitheringAlgorithm::JarvisJudiceNinke:
|
||||||
|
s_dither.reset(
|
||||||
|
new render::ErrorDiffusionDither(render::ErrorDiffusionType::JarvisJudiceNinke, -1));
|
||||||
|
break;
|
||||||
|
case render::DitheringAlgorithm::Stucki:
|
||||||
|
s_dither.reset(new render::ErrorDiffusionDither(render::ErrorDiffusionType::Stucki, -1));
|
||||||
|
break;
|
||||||
|
case render::DitheringAlgorithm::Atkinson:
|
||||||
|
s_dither.reset(new render::ErrorDiffusionDither(render::ErrorDiffusionType::Atkinson, -1));
|
||||||
|
break;
|
||||||
|
case render::DitheringAlgorithm::Burkes:
|
||||||
|
s_dither.reset(new render::ErrorDiffusionDither(render::ErrorDiffusionType::Burkes, -1));
|
||||||
|
break;
|
||||||
|
case render::DitheringAlgorithm::Sierra:
|
||||||
|
s_dither.reset(new render::ErrorDiffusionDither(render::ErrorDiffusionType::Sierra, -1));
|
||||||
|
break;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -231,9 +231,24 @@ void DitheringSelector::regenerate(int selectedItemIndex)
|
||||||
Console::showException(e);
|
Console::showException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addItem(new DitherItem(render::DitheringAlgorithm::ErrorDiffusion,
|
addItem(new DitherItem(render::DitheringAlgorithm::FloydSteinberg,
|
||||||
render::DitheringMatrix(),
|
render::DitheringMatrix(),
|
||||||
Strings::dithering_selector_floyd_steinberg()));
|
Strings::dithering_selector_floyd_steinberg()));
|
||||||
|
addItem(new DitherItem(render::DitheringAlgorithm::JarvisJudiceNinke,
|
||||||
|
render::DitheringMatrix(),
|
||||||
|
Strings::dithering_selector_jarvis_judice_ninke()));
|
||||||
|
addItem(new DitherItem(render::DitheringAlgorithm::Stucki,
|
||||||
|
render::DitheringMatrix(),
|
||||||
|
Strings::dithering_selector_stucki()));
|
||||||
|
addItem(new DitherItem(render::DitheringAlgorithm::Atkinson,
|
||||||
|
render::DitheringMatrix(),
|
||||||
|
Strings::dithering_selector_atkinson()));
|
||||||
|
addItem(new DitherItem(render::DitheringAlgorithm::Burkes,
|
||||||
|
render::DitheringMatrix(),
|
||||||
|
Strings::dithering_selector_burkes()));
|
||||||
|
addItem(new DitherItem(render::DitheringAlgorithm::Sierra,
|
||||||
|
render::DitheringMatrix(),
|
||||||
|
Strings::dithering_selector_sierra()));
|
||||||
break;
|
break;
|
||||||
case SelectMatrix:
|
case SelectMatrix:
|
||||||
addItem(
|
addItem(
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
# Copyright (C) 2001-2018 David Capello
|
# Copyright (C) 2001-2018 David Capello
|
||||||
|
|
||||||
add_library(render-lib
|
add_library(render-lib
|
||||||
|
dithering_algorithm.cpp
|
||||||
error_diffusion.cpp
|
error_diffusion.cpp
|
||||||
get_sprite_pixel.cpp
|
get_sprite_pixel.cpp
|
||||||
gradient.cpp
|
gradient.cpp
|
||||||
|
|
|
@ -18,24 +18,29 @@ class Dithering {
|
||||||
public:
|
public:
|
||||||
Dithering(DitheringAlgorithm algorithm = DitheringAlgorithm::None,
|
Dithering(DitheringAlgorithm algorithm = DitheringAlgorithm::None,
|
||||||
const DitheringMatrix& matrix = DitheringMatrix(),
|
const DitheringMatrix& matrix = DitheringMatrix(),
|
||||||
|
bool zigzag = true,
|
||||||
double factor = 1.0)
|
double factor = 1.0)
|
||||||
: m_algorithm(algorithm)
|
: m_algorithm(algorithm)
|
||||||
, m_matrix(matrix)
|
, m_matrix(matrix)
|
||||||
|
, m_zigzag(zigzag)
|
||||||
, m_factor(factor)
|
, m_factor(factor)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
DitheringAlgorithm algorithm() const { return m_algorithm; }
|
DitheringAlgorithm algorithm() const { return m_algorithm; }
|
||||||
DitheringMatrix matrix() const { return m_matrix; }
|
DitheringMatrix matrix() const { return m_matrix; }
|
||||||
|
bool zigzag() const { return m_zigzag; }
|
||||||
double factor() const { return m_factor; }
|
double factor() const { return m_factor; }
|
||||||
|
|
||||||
void algorithm(const DitheringAlgorithm algorithm) { m_algorithm = algorithm; }
|
void algorithm(const DitheringAlgorithm algorithm) { m_algorithm = algorithm; }
|
||||||
void matrix(const DitheringMatrix& matrix) { m_matrix = matrix; }
|
void matrix(const DitheringMatrix& matrix) { m_matrix = matrix; }
|
||||||
|
void zigzag(bool zigzag) { m_zigzag = zigzag; }
|
||||||
void factor(const double factor) { m_factor = factor; }
|
void factor(const double factor) { m_factor = factor; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DitheringAlgorithm m_algorithm;
|
DitheringAlgorithm m_algorithm;
|
||||||
DitheringMatrix m_matrix;
|
DitheringMatrix m_matrix;
|
||||||
|
bool m_zigzag;
|
||||||
double m_factor;
|
double m_factor;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
// Aseprite
|
||||||
|
// Copyright (C) 2025 Igara Studio S.A.
|
||||||
|
//
|
||||||
|
// This program is distributed under the terms of
|
||||||
|
// the End-User License Agreement for Aseprite.
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "base/string.h"
|
||||||
|
#include "render/dithering_algorithm.h"
|
||||||
|
|
||||||
|
namespace render {
|
||||||
|
|
||||||
|
static const std::unordered_map<DitheringAlgorithm, std::string> names = {
|
||||||
|
{ DitheringAlgorithm::None, "none" },
|
||||||
|
{ DitheringAlgorithm::Ordered, "ordered" },
|
||||||
|
{ DitheringAlgorithm::Old, "old" },
|
||||||
|
{ DitheringAlgorithm::FloydSteinberg, "floyd-steinberg" },
|
||||||
|
{ DitheringAlgorithm::FloydSteinberg, "error-diffusion" }, // alias
|
||||||
|
{ DitheringAlgorithm::JarvisJudiceNinke, "jarvis-judice-ninke" },
|
||||||
|
{ DitheringAlgorithm::Stucki, "stucki" },
|
||||||
|
{ DitheringAlgorithm::Atkinson, "atkinson" },
|
||||||
|
{ DitheringAlgorithm::Burkes, "burkes" },
|
||||||
|
{ DitheringAlgorithm::Sierra, "sierra" },
|
||||||
|
};
|
||||||
|
|
||||||
|
bool DitheringAlgorithmIsDiffusion(DitheringAlgorithm algo)
|
||||||
|
{
|
||||||
|
switch (algo) {
|
||||||
|
case DitheringAlgorithm::None:
|
||||||
|
case DitheringAlgorithm::Ordered:
|
||||||
|
case DitheringAlgorithm::Old: return false;
|
||||||
|
default: return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string DitheringAlgorithmToString(DitheringAlgorithm algo)
|
||||||
|
{
|
||||||
|
auto it = names.find(algo);
|
||||||
|
return (it != names.end()) ? it->second : "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
DitheringAlgorithm DitheringAlgorithmFromString(const std::string& name)
|
||||||
|
{
|
||||||
|
auto it = std::find_if(names.begin(), names.end(), [name](const auto& pair) {
|
||||||
|
return base::utf8_icmp(pair.second, name) == 0;
|
||||||
|
});
|
||||||
|
return (it != names.end()) ? it->first : DitheringAlgorithm::Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace render
|
|
@ -9,6 +9,8 @@
|
||||||
#define RENDER_DITHERING_METHOD_H_INCLUDED
|
#define RENDER_DITHERING_METHOD_H_INCLUDED
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace render {
|
namespace render {
|
||||||
|
|
||||||
// Dithering algorithms
|
// Dithering algorithms
|
||||||
|
@ -16,9 +18,19 @@ enum class DitheringAlgorithm {
|
||||||
None,
|
None,
|
||||||
Ordered,
|
Ordered,
|
||||||
Old,
|
Old,
|
||||||
ErrorDiffusion,
|
FloydSteinberg,
|
||||||
|
JarvisJudiceNinke,
|
||||||
|
Stucki,
|
||||||
|
Atkinson,
|
||||||
|
Burkes,
|
||||||
|
Sierra,
|
||||||
|
Unknown
|
||||||
};
|
};
|
||||||
|
|
||||||
|
bool DitheringAlgorithmIsDiffusion(DitheringAlgorithm algo);
|
||||||
|
std::string DitheringAlgorithmToString(DitheringAlgorithm algo);
|
||||||
|
DitheringAlgorithm DitheringAlgorithmFromString(const std::string& name);
|
||||||
|
|
||||||
} // namespace render
|
} // namespace render
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -15,23 +15,139 @@
|
||||||
#include "gfx/rgb.h"
|
#include "gfx/rgb.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace render {
|
namespace render {
|
||||||
|
|
||||||
ErrorDiffusionDither::ErrorDiffusionDither(int transparentIndex)
|
// Predefined error diffusion algorithms
|
||||||
|
class ErrorDiffusionMatrices {
|
||||||
|
public:
|
||||||
|
static const ErrorDiffusionMatrix& getFloydSteinberg()
|
||||||
|
{
|
||||||
|
static const ErrorDiffusionMatrix matrix(3,
|
||||||
|
2,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
{
|
||||||
|
{ 0, 0, 7 },
|
||||||
|
{ 3, 5, 1 }
|
||||||
|
},
|
||||||
|
16);
|
||||||
|
return matrix;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const ErrorDiffusionMatrix& getJarvisJudiceNinke()
|
||||||
|
{
|
||||||
|
static const ErrorDiffusionMatrix matrix(5,
|
||||||
|
3,
|
||||||
|
2,
|
||||||
|
0,
|
||||||
|
{
|
||||||
|
{ 0, 0, 0, 7, 5 },
|
||||||
|
{ 3, 5, 7, 5, 3 },
|
||||||
|
{ 1, 3, 5, 3, 1 }
|
||||||
|
},
|
||||||
|
48);
|
||||||
|
return matrix;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const ErrorDiffusionMatrix& getStucki()
|
||||||
|
{
|
||||||
|
static const ErrorDiffusionMatrix matrix(5,
|
||||||
|
3,
|
||||||
|
2,
|
||||||
|
0,
|
||||||
|
{
|
||||||
|
{ 0, 0, 0, 8, 4 },
|
||||||
|
{ 2, 4, 8, 4, 2 },
|
||||||
|
{ 1, 2, 4, 2, 1 }
|
||||||
|
},
|
||||||
|
42);
|
||||||
|
return matrix;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const ErrorDiffusionMatrix& getAtkinson()
|
||||||
|
{
|
||||||
|
static const ErrorDiffusionMatrix matrix(4,
|
||||||
|
3,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
{
|
||||||
|
{ 0, 0, 1, 1 },
|
||||||
|
{ 1, 1, 1, 0 },
|
||||||
|
{ 0, 1, 0, 0 }
|
||||||
|
},
|
||||||
|
8);
|
||||||
|
return matrix;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const ErrorDiffusionMatrix& getBurkes()
|
||||||
|
{
|
||||||
|
static const ErrorDiffusionMatrix matrix(5,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
0,
|
||||||
|
{
|
||||||
|
{ 0, 0, 0, 8, 4 },
|
||||||
|
{ 2, 4, 8, 4, 2 }
|
||||||
|
},
|
||||||
|
32);
|
||||||
|
return matrix;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const ErrorDiffusionMatrix& getSierra()
|
||||||
|
{
|
||||||
|
static const ErrorDiffusionMatrix matrix(5,
|
||||||
|
3,
|
||||||
|
2,
|
||||||
|
0,
|
||||||
|
{
|
||||||
|
{ 0, 0, 0, 5, 3 },
|
||||||
|
{ 2, 4, 5, 4, 2 },
|
||||||
|
{ 0, 2, 3, 2, 0 }
|
||||||
|
},
|
||||||
|
32);
|
||||||
|
return matrix;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ErrorDiffusionDither::ErrorDiffusionDither(ErrorDiffusionType type, int transparentIndex)
|
||||||
: m_transparentIndex(transparentIndex)
|
: m_transparentIndex(transparentIndex)
|
||||||
|
, m_diffusionType(type)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ErrorDiffusionMatrix& ErrorDiffusionDither::getCurrentMatrix() const
|
||||||
|
{
|
||||||
|
switch (m_diffusionType) {
|
||||||
|
case ErrorDiffusionType::JarvisJudiceNinke:
|
||||||
|
return ErrorDiffusionMatrices::getJarvisJudiceNinke();
|
||||||
|
case ErrorDiffusionType::Stucki: return ErrorDiffusionMatrices::getStucki();
|
||||||
|
case ErrorDiffusionType::Atkinson: return ErrorDiffusionMatrices::getAtkinson();
|
||||||
|
case ErrorDiffusionType::Burkes: return ErrorDiffusionMatrices::getBurkes();
|
||||||
|
case ErrorDiffusionType::Sierra: return ErrorDiffusionMatrices::getSierra();
|
||||||
|
case ErrorDiffusionType::FloydSteinberg: return ErrorDiffusionMatrices::getFloydSteinberg();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ErrorDiffusionDither::start(const doc::Image* srcImage,
|
void ErrorDiffusionDither::start(const doc::Image* srcImage,
|
||||||
doc::Image* dstImage,
|
doc::Image* dstImage,
|
||||||
const double factor)
|
const double factor)
|
||||||
{
|
{
|
||||||
m_srcImage = srcImage;
|
m_srcImage = srcImage;
|
||||||
m_width = 2 + srcImage->width();
|
m_width = 2 + srcImage->width();
|
||||||
|
|
||||||
|
// Get the current matrix to determine buffer size needed
|
||||||
|
const ErrorDiffusionMatrix& matrix = getCurrentMatrix();
|
||||||
|
const int bufferRows = matrix.height;
|
||||||
|
|
||||||
|
// Resize error buffers to accommodate the matrix height
|
||||||
|
const std::vector<int>::size_type bufferSize = m_width * bufferRows;
|
||||||
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(bufferSize, 0);
|
||||||
|
|
||||||
m_lastY = -1;
|
m_lastY = -1;
|
||||||
|
m_currentRowOffset = 0;
|
||||||
m_factor = int(factor * 100.0);
|
m_factor = int(factor * 100.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,30 +158,39 @@ void ErrorDiffusionDither::finish()
|
||||||
doc::color_t ErrorDiffusionDither::ditherRgbToIndex2D(const int x,
|
doc::color_t ErrorDiffusionDither::ditherRgbToIndex2D(const int x,
|
||||||
const int y,
|
const int y,
|
||||||
const doc::RgbMap* rgbmap,
|
const doc::RgbMap* rgbmap,
|
||||||
const doc::Palette* palette)
|
const doc::Palette* palette,
|
||||||
|
const int direction)
|
||||||
{
|
{
|
||||||
|
const ErrorDiffusionMatrix& matrix = getCurrentMatrix();
|
||||||
|
|
||||||
if (y != m_lastY) {
|
if (y != m_lastY) {
|
||||||
for (int i = 0; i < kChannels; ++i) {
|
// Instead of shifting all rows, just advance the circular buffer
|
||||||
int* row0 = &m_err[i][0];
|
// and clear the row that will be reused
|
||||||
int* row1 = row0 + m_width;
|
m_currentRowOffset = (m_currentRowOffset + 1) % matrix.height;
|
||||||
int* end1 = row1 + m_width;
|
|
||||||
std::copy(row1, end1, row0);
|
// Clear only the row that will be used as the "last" row
|
||||||
std::fill(row1, end1, 0);
|
const int clearRowIndex = (m_currentRowOffset + matrix.height - 1) % matrix.height;
|
||||||
|
for (int c = 0; c < kChannels; ++c) {
|
||||||
|
int* rowToClear = &m_err[c][m_width * clearRowIndex];
|
||||||
|
std::fill(rowToClear, rowToClear + m_width, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_lastY = y;
|
m_lastY = y;
|
||||||
}
|
}
|
||||||
|
|
||||||
doc::color_t color = doc::get_pixel_fast<doc::RgbTraits>(m_srcImage, x, y);
|
doc::color_t color = doc::get_pixel_fast<doc::RgbTraits>(m_srcImage, x, y);
|
||||||
|
|
||||||
// Get RGB values + quatization error
|
// Get RGB values + quantization error
|
||||||
int v[kChannels] = { doc::rgba_getr(color),
|
int v[kChannels] = { doc::rgba_getr(color),
|
||||||
doc::rgba_getg(color),
|
doc::rgba_getg(color),
|
||||||
doc::rgba_getb(color),
|
doc::rgba_getb(color),
|
||||||
doc::rgba_geta(color) };
|
doc::rgba_geta(color) };
|
||||||
for (int i = 0; i < kChannels; ++i) {
|
|
||||||
v[i] += m_err[i][x + 1];
|
// Add accumulated error (16-bit fixed point) and convert to 0..255
|
||||||
v[i] = std::clamp(v[i], 0, 255);
|
for (int c = 0; c < kChannels; ++c)
|
||||||
}
|
v[c] = std::clamp(((v[c] << 16) + m_err[c][m_width * m_currentRowOffset + x + 1] + 32767) >> 16,
|
||||||
|
0,
|
||||||
|
255);
|
||||||
|
|
||||||
const doc::color_t index = (rgbmap ?
|
const doc::color_t index = (rgbmap ?
|
||||||
rgbmap->mapColor(v[0], v[1], v[2], v[3]) :
|
rgbmap->mapColor(v[0], v[1], v[2], v[3]) :
|
||||||
|
@ -82,26 +207,36 @@ doc::color_t ErrorDiffusionDither::ditherRgbToIndex2D(const int x,
|
||||||
v[2] - doc::rgba_getb(palColor),
|
v[2] - doc::rgba_getb(palColor),
|
||||||
v[3] - doc::rgba_geta(palColor) };
|
v[3] - doc::rgba_geta(palColor) };
|
||||||
|
|
||||||
// TODO using Floyd-Steinberg matrix here but it should be configurable
|
const int srcWidth = m_srcImage->width();
|
||||||
for (int i = 0; i < kChannels; ++i) {
|
|
||||||
int* err = &m_err[i][x];
|
|
||||||
const int q = quantError[i] * m_factor / 100;
|
|
||||||
const int a = q * 7 / 16;
|
|
||||||
const int b = q * 3 / 16;
|
|
||||||
const int c = q * 5 / 16;
|
|
||||||
const int d = q * 1 / 16;
|
|
||||||
|
|
||||||
if (y & 1) {
|
// Distribute error using the configurable matrix
|
||||||
err[0] += a;
|
for (int c = 0; c < kChannels; ++c) {
|
||||||
err[m_width + 2] += b;
|
const int qerr = quantError[c] * m_factor / 100;
|
||||||
err[m_width + 1] += c;
|
|
||||||
err[m_width] += d;
|
for (int my = 0; my < matrix.height; ++my) {
|
||||||
}
|
// Use circular buffer indexing
|
||||||
else {
|
const int bufferRow = (m_currentRowOffset + my) % matrix.height;
|
||||||
err[+2] += a;
|
const int bufferRowIndex = bufferRow * m_width;
|
||||||
err[m_width] += b;
|
|
||||||
err[m_width + 1] += c;
|
for (int mx = 0; mx < matrix.width; ++mx) {
|
||||||
err[m_width + 2] += d;
|
const int coeff = direction > 0 ? matrix.coefficients[my][mx] :
|
||||||
|
matrix.coefficients[my][matrix.width - 1 - mx];
|
||||||
|
if (coeff == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const int errorPixelX = x + mx - matrix.centerX;
|
||||||
|
const int errorPixelY = y + my - matrix.centerY;
|
||||||
|
|
||||||
|
// Check bounds
|
||||||
|
if (errorPixelX < 0 || errorPixelX >= srcWidth)
|
||||||
|
continue;
|
||||||
|
if (errorPixelY < y)
|
||||||
|
continue; // Don't go backwards
|
||||||
|
|
||||||
|
// Calculate error as 16-bit fixed point
|
||||||
|
const int errorValue = ((qerr * coeff) << 16) / matrix.divisor;
|
||||||
|
m_err[c][bufferRowIndex + errorPixelX + 1] += errorValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,22 +16,58 @@
|
||||||
|
|
||||||
namespace render {
|
namespace render {
|
||||||
|
|
||||||
|
enum class ErrorDiffusionType : uint8_t {
|
||||||
|
FloydSteinberg,
|
||||||
|
JarvisJudiceNinke,
|
||||||
|
Stucki,
|
||||||
|
Atkinson,
|
||||||
|
Burkes,
|
||||||
|
Sierra
|
||||||
|
};
|
||||||
|
|
||||||
|
// Error diffusion matrix structure
|
||||||
|
struct ErrorDiffusionMatrix {
|
||||||
|
int width, height;
|
||||||
|
int centerX, centerY;
|
||||||
|
std::vector<std::vector<int>> coefficients;
|
||||||
|
int divisor;
|
||||||
|
|
||||||
|
ErrorDiffusionMatrix(int w,
|
||||||
|
int h,
|
||||||
|
int cx,
|
||||||
|
int cy,
|
||||||
|
const std::vector<std::vector<int>>& coeff,
|
||||||
|
int div)
|
||||||
|
: width(w)
|
||||||
|
, height(h)
|
||||||
|
, centerX(cx)
|
||||||
|
, centerY(cy)
|
||||||
|
, coefficients(coeff)
|
||||||
|
, divisor(div)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class ErrorDiffusionDither : public DitheringAlgorithmBase {
|
class ErrorDiffusionDither : public DitheringAlgorithmBase {
|
||||||
public:
|
public:
|
||||||
ErrorDiffusionDither(int transparentIndex = -1);
|
ErrorDiffusionDither(ErrorDiffusionType type, int transparentIndex);
|
||||||
int dimensions() const override { return 2; }
|
int dimensions() const override { return 2; }
|
||||||
bool zigZag() const override { return true; }
|
|
||||||
void start(const doc::Image* srcImage, doc::Image* dstImage, const double factor) override;
|
void start(const doc::Image* srcImage, doc::Image* dstImage, const double factor) override;
|
||||||
void finish() override;
|
void finish() override;
|
||||||
doc::color_t ditherRgbToIndex2D(const int x,
|
doc::color_t ditherRgbToIndex2D(const int x,
|
||||||
const int y,
|
const int y,
|
||||||
const doc::RgbMap* rgbmap,
|
const doc::RgbMap* rgbmap,
|
||||||
const doc::Palette* palette) override;
|
const doc::Palette* palette,
|
||||||
|
const int direction) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
const ErrorDiffusionMatrix& getCurrentMatrix() const;
|
||||||
|
|
||||||
int m_transparentIndex;
|
int m_transparentIndex;
|
||||||
|
ErrorDiffusionType m_diffusionType;
|
||||||
const doc::Image* m_srcImage;
|
const doc::Image* m_srcImage;
|
||||||
int m_width, m_lastY;
|
int m_width, m_lastY;
|
||||||
|
int m_currentRowOffset;
|
||||||
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;
|
int m_factor;
|
||||||
|
|
|
@ -240,47 +240,38 @@ void dither_rgb_image_to_indexed(DitheringAlgorithmBase& algorithm,
|
||||||
ASSERT(srcIt != srcBits.end());
|
ASSERT(srcIt != srcBits.end());
|
||||||
ASSERT(dstIt != dstBits.end());
|
ASSERT(dstIt != dstBits.end());
|
||||||
*dstIt = algorithm.ditherRgbPixelToIndex(dithering.matrix(), *srcIt, x, y, rgbmap, palette);
|
*dstIt = algorithm.ditherRgbPixelToIndex(dithering.matrix(), *srcIt, x, y, rgbmap, palette);
|
||||||
|
|
||||||
if (delegate) {
|
|
||||||
if (!delegate->continueTask())
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (delegate) {
|
if (delegate) {
|
||||||
|
if (!delegate->continueTask())
|
||||||
|
return;
|
||||||
delegate->notifyTaskProgress(double(y + 1) / double(h));
|
delegate->notifyTaskProgress(double(y + 1) / double(h));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
auto dstIt = doc::get_pixel_address_fast<doc::IndexedTraits>(dstImage, 0, 0);
|
auto dstIt = doc::get_pixel_address_fast<doc::IndexedTraits>(dstImage, 0, 0);
|
||||||
const bool zigZag = algorithm.zigZag();
|
auto zigZag = dithering.zigzag();
|
||||||
|
|
||||||
for (int y = 0; y < h; ++y) {
|
for (int y = 0; y < h; ++y) {
|
||||||
if (zigZag && (y & 1)) { // Odd row: go from right-to-left
|
if (zigZag && (y & 1)) { // Odd row: go from right-to-left
|
||||||
dstIt += w - 1;
|
dstIt += w - 1;
|
||||||
for (int x = w - 1; x >= 0; --x, --dstIt) {
|
for (int x = w - 1; x >= 0; --x, --dstIt) {
|
||||||
ASSERT(dstIt == doc::get_pixel_address_fast<doc::IndexedTraits>(dstImage, x, y));
|
ASSERT(dstIt == doc::get_pixel_address_fast<doc::IndexedTraits>(dstImage, x, y));
|
||||||
*dstIt = algorithm.ditherRgbToIndex2D(x, y, rgbmap, palette);
|
*dstIt = algorithm.ditherRgbToIndex2D(x, y, rgbmap, palette, -1);
|
||||||
if (delegate) {
|
|
||||||
if (!delegate->continueTask())
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
dstIt += w + 1;
|
dstIt += w + 1;
|
||||||
}
|
}
|
||||||
else { // Even row: go fromo left-to-right
|
else { // Even row: go from left-to-right
|
||||||
for (int x = 0; x < w; ++x, ++dstIt) {
|
for (int x = 0; x < w; ++x, ++dstIt) {
|
||||||
ASSERT(dstIt == doc::get_pixel_address_fast<doc::IndexedTraits>(dstImage, x, y));
|
ASSERT(dstIt == doc::get_pixel_address_fast<doc::IndexedTraits>(dstImage, x, y));
|
||||||
*dstIt = algorithm.ditherRgbToIndex2D(x, y, rgbmap, palette);
|
*dstIt = algorithm.ditherRgbToIndex2D(x, y, rgbmap, palette, +1);
|
||||||
|
|
||||||
if (delegate) {
|
|
||||||
if (!delegate->continueTask())
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (delegate) {
|
if (delegate) {
|
||||||
|
if (!delegate->continueTask())
|
||||||
|
return;
|
||||||
delegate->notifyTaskProgress(double(y + 1) / double(h));
|
delegate->notifyTaskProgress(double(y + 1) / double(h));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,6 @@ public:
|
||||||
virtual ~DitheringAlgorithmBase() {}
|
virtual ~DitheringAlgorithmBase() {}
|
||||||
|
|
||||||
virtual int dimensions() const { return 1; }
|
virtual int dimensions() const { return 1; }
|
||||||
virtual bool zigZag() const { return false; }
|
|
||||||
|
|
||||||
virtual void start(const doc::Image* srcImage, doc::Image* dstImage, const double factor) {}
|
virtual void start(const doc::Image* srcImage, doc::Image* dstImage, const double factor) {}
|
||||||
|
|
||||||
|
@ -46,7 +45,8 @@ public:
|
||||||
virtual doc::color_t ditherRgbToIndex2D(const int x,
|
virtual doc::color_t ditherRgbToIndex2D(const int x,
|
||||||
const int y,
|
const int y,
|
||||||
const doc::RgbMap* rgbmap,
|
const doc::RgbMap* rgbmap,
|
||||||
const doc::Palette* palette)
|
const doc::Palette* palette,
|
||||||
|
const int direction)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -159,8 +159,29 @@ Image* convert_pixel_format(const Image* image,
|
||||||
case DitheringAlgorithm::Old:
|
case DitheringAlgorithm::Old:
|
||||||
dither.reset(new OrderedDither(is_background ? -1 : new_mask_color));
|
dither.reset(new OrderedDither(is_background ? -1 : new_mask_color));
|
||||||
break;
|
break;
|
||||||
case DitheringAlgorithm::ErrorDiffusion:
|
case DitheringAlgorithm::FloydSteinberg:
|
||||||
dither.reset(new ErrorDiffusionDither(is_background ? -1 : new_mask_color));
|
dither.reset(new ErrorDiffusionDither(ErrorDiffusionType::FloydSteinberg,
|
||||||
|
is_background ? -1 : new_mask_color));
|
||||||
|
break;
|
||||||
|
case DitheringAlgorithm::JarvisJudiceNinke:
|
||||||
|
dither.reset(new ErrorDiffusionDither(ErrorDiffusionType::JarvisJudiceNinke,
|
||||||
|
is_background ? -1 : new_mask_color));
|
||||||
|
break;
|
||||||
|
case DitheringAlgorithm::Stucki:
|
||||||
|
dither.reset(new ErrorDiffusionDither(ErrorDiffusionType::Stucki,
|
||||||
|
is_background ? -1 : new_mask_color));
|
||||||
|
break;
|
||||||
|
case DitheringAlgorithm::Atkinson:
|
||||||
|
dither.reset(new ErrorDiffusionDither(ErrorDiffusionType::Atkinson,
|
||||||
|
is_background ? -1 : new_mask_color));
|
||||||
|
break;
|
||||||
|
case DitheringAlgorithm::Burkes:
|
||||||
|
dither.reset(new ErrorDiffusionDither(ErrorDiffusionType::Burkes,
|
||||||
|
is_background ? -1 : new_mask_color));
|
||||||
|
break;
|
||||||
|
case DitheringAlgorithm::Sierra:
|
||||||
|
dither.reset(new ErrorDiffusionDither(ErrorDiffusionType::Sierra,
|
||||||
|
is_background ? -1 : new_mask_color));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (dither)
|
if (dither)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Aseprite Rener Library
|
// Aseprite Render Library
|
||||||
// Copyright (c) 2019-2021 Igara Studio S.A.
|
// Copyright (c) 2019-2021 Igara Studio S.A.
|
||||||
// Copyright (c) 2001-2017 David Capello
|
// Copyright (c) 2001-2017 David Capello
|
||||||
//
|
//
|
||||||
|
|
Loading…
Reference in New Issue