Add Floyd-Steinberg dithering for RGBA -> Indexed conversion

This commit is contained in:
David Capello 2019-04-01 21:44:06 -03:00
parent c0b4224f32
commit 20b8ee0e57
13 changed files with 239 additions and 23 deletions

View File

@ -232,6 +232,7 @@ ChangePixelFormat_Grayscale = Grayscale
ChangePixelFormat_Indexed = Indexed
ChangePixelFormat_Indexed_OrderedDithering = Indexed with Ordered Dithering
ChangePixelFormat_Indexed_OldDithering = Indexed with Old Dithering
ChangePixelFormat_Indexed_ErrorDifussion = Indexed with Floyd-Steinberg Error Diffusion Dithering
ChangePixelFormat_Indexed = Indexed
ChangePixelFormat_MoreOptions = More Options
Clear = Clear

View File

@ -368,10 +368,12 @@ void CliProcessor::process(Context* ctx)
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("--dithering-algorithm needs a valid algorithm name\n"
"Usage: --dithering-algorithm <algorithm>\n"
"Where <algorithm> can be none, ordered, or old");
"Where <algorithm> can be none, ordered, old, or error-diffusion");
}
// --dithering-matrix <id>
else if (opt == &m_options.ditheringMatrix()) {
@ -399,6 +401,9 @@ void CliProcessor::process(Context* ctx)
case render::DitheringAlgorithm::Old:
params.set("dithering", "old");
break;
case render::DitheringAlgorithm::ErrorDiffusion:
params.set("dithering", "error-diffusion");
break;
}
if (ditheringAlgorithm != render::DitheringAlgorithm::None &&

View File

@ -381,6 +381,8 @@ void ChangePixelFormatCommand::onLoadParams(const Params& params)
m_ditheringAlgorithm = render::DitheringAlgorithm::Ordered;
else if (dithering == "old")
m_ditheringAlgorithm = render::DitheringAlgorithm::Old;
else if (dithering == "error-diffusion")
m_ditheringAlgorithm = render::DitheringAlgorithm::ErrorDiffusion;
else
m_ditheringAlgorithm = render::DitheringAlgorithm::None;
@ -523,6 +525,9 @@ std::string ChangePixelFormatCommand::onGetFriendlyName() const
case render::DitheringAlgorithm::Old:
conversion = Strings::commands_ChangePixelFormat_Indexed_OldDithering();
break;
case render::DitheringAlgorithm::ErrorDiffusion:
conversion = Strings::commands_ChangePixelFormat_Indexed_ErrorDifussion();
break;
}
break;
}

View File

@ -1736,6 +1736,9 @@ render::DitheringAlgorithmBase* ContextBar::ditheringAlgorithm()
case render::DitheringAlgorithm::Old:
s_dither.reset(new render::OrderedDither(-1));
break;
case render::DitheringAlgorithm::ErrorDiffusion:
s_dither.reset(new render::ErrorDiffusionDither(-1));
break;
}
return s_dither.get();

View File

@ -199,6 +199,11 @@ void DitheringSelector::regenerate()
it.matrix(),
"Old Dithering+" + it.name()));
}
addItem(
new DitherItem(
render::DitheringAlgorithm::ErrorDiffusion,
render::DitheringMatrix(),
"Floyd-Steinberg Error Diffusion Dithering"));
break;
case SelectMatrix:
addItem(new DitherItem(render::DitheringMatrix(), "No Dithering"));

View File

@ -2,6 +2,7 @@
# Copyright (C) 2001-2017 David Capello
add_library(render-lib
error_diffusion.cpp
get_sprite_pixel.cpp
gradient.cpp
ordered_dither.cpp

View File

@ -1,4 +1,5 @@
// Aseprite Render Library
// Copyright (c) 2019 Igara Studio S.A.
// Copyright (c) 2017 David Capello
//
// This file is released under the terms of the MIT license.
@ -10,6 +11,7 @@
#include "render/dithering_algorithm.h"
#include "render/dithering_matrix.h"
#include "render/error_diffusion.h"
#include "render/ordered_dither.h"
#endif

View File

@ -1,4 +1,5 @@
// Aseprite Render Library
// Copyright (c) 2019 Igara Studio S.A.
// Copyright (c) 2001-2017 David Capello
//
// This file is released under the terms of the MIT license.
@ -15,6 +16,7 @@ namespace render {
None,
Ordered,
Old,
ErrorDiffusion,
};
} // namespace render

View File

@ -0,0 +1,104 @@
// Aseprite Render Library
// Copyright (c) 2019 Igara Studio S.A
// Copyright (c) 2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "render/error_diffusion.h"
#include "gfx/hsl.h"
#include "gfx/rgb.h"
#include <algorithm>
namespace render {
ErrorDiffusionDither::ErrorDiffusionDither(int transparentIndex)
: m_transparentIndex(transparentIndex)
{
}
void ErrorDiffusionDither::start(
const doc::Image* srcImage,
doc::Image* dstImage)
{
m_srcImage = srcImage;
m_width = 2+srcImage->width();
for (int i=0; i<kChannels; ++i) {
m_err[i].resize(m_width*2);
m_err[i].clear();
}
m_lastY = -1;
}
void ErrorDiffusionDither::finish()
{
}
doc::color_t ErrorDiffusionDither::ditherRgbToIndex2D(
const int x, const int y,
const doc::RgbMap* rgbmap,
const doc::Palette* palette)
{
if (y != m_lastY) {
for (int i=0; i<kChannels; ++i) {
int* row0 = &m_err[i][0];
int* row1 = row0 + m_width;
int* end1 = row1 + m_width;
std::copy(row1, end1, row0);
std::fill(row1, end1, 0);
}
m_lastY = y;
}
doc::color_t color =
doc::get_pixel_fast<doc::RgbTraits>(m_srcImage, x, y);
// Get RGB values + quatization error
int v[kChannels] = {
doc::rgba_getr(color),
doc::rgba_getg(color),
doc::rgba_getb(color),
doc::rgba_geta(color)
};
int u[kChannels];
for (int i=0; i<kChannels; ++i) {
v[i] += m_err[i][x+1] / 16;
u[i] = MID(0, v[i], 255);
}
const doc::color_t index =
(rgbmap ? rgbmap->mapColor(u[0], u[1], u[2], u[3]):
palette->findBestfit(u[0], u[1], u[2], u[3], m_transparentIndex));
doc::color_t palColor = palette->getEntry(index);
if (m_transparentIndex == index || doc::rgba_geta(palColor) == 0) {
// "color" without alpha
palColor = (color & doc::rgba_rgb_mask);
}
const int quantError[kChannels] = {
v[0] - doc::rgba_getr(palColor),
v[1] - doc::rgba_getg(palColor),
v[2] - doc::rgba_getb(palColor),
v[3] - doc::rgba_geta(palColor)
};
// TODO using Floyd-Steinberg matrix here but it should be configurable
for (int i=0; i<kChannels; ++i) {
int* err = &m_err[i][x];
err[ +2] += quantError[i] * 7;
err[m_width ] += quantError[i] * 3;
err[m_width+1] += quantError[i] * 5;
err[m_width+2] += quantError[i] * 1;
}
return index;
}
} // namespace render

View File

@ -0,0 +1,41 @@
// Aseprite Render Library
// Copyright (c) 2019 Igara Studio S.A
// Copyright (c) 2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef RENDER_ERROR_DIFFUSION_H_INCLUDED
#define RENDER_ERROR_DIFFUSION_H_INCLUDED
#pragma once
#include "doc/image_ref.h"
#include "render/ordered_dither.h"
#include <vector>
namespace render {
class ErrorDiffusionDither : public DitheringAlgorithmBase {
public:
ErrorDiffusionDither(int transparentIndex = -1);
int dimensions() const override { return 2; }
void start(
const doc::Image* srcImage,
doc::Image* dstImage) override;
void finish() override;
doc::color_t ditherRgbToIndex2D(
const int x, const int y,
const doc::RgbMap* rgbmap,
const doc::Palette* palette) override;
private:
int m_transparentIndex;
const doc::Image* m_srcImage;
int m_width, m_lastY;
static const int kChannels = 4;
std::vector<int> m_err[kChannels];
};
} // namespace render
#endif

View File

@ -1,4 +1,5 @@
// Aseprite Render Library
// Copyright (c) 2019 Igara Studio S.A.
// Copyright (c) 2017 David Capello
//
// This file is released under the terms of the MIT license.
@ -230,35 +231,62 @@ void dither_rgb_image_to_indexed(
const DitheringMatrix& matrix,
const doc::Image* srcImage,
doc::Image* dstImage,
int u, int v,
const doc::RgbMap* rgbmap,
const doc::Palette* palette,
TaskDelegate* delegate)
{
const doc::LockImageBits<doc::RgbTraits> srcBits(srcImage);
doc::LockImageBits<doc::IndexedTraits> dstBits(dstImage);
auto srcIt = srcBits.begin();
auto dstIt = dstBits.begin();
int w = srcImage->width();
int h = srcImage->height();
const int w = srcImage->width();
const int h = srcImage->height();
for (int y=0; y<h; ++y) {
for (int x=0; x<w; ++x, ++srcIt, ++dstIt) {
ASSERT(srcIt != srcBits.end());
ASSERT(dstIt != dstBits.end());
*dstIt = algorithm.ditherRgbPixelToIndex(matrix, *srcIt, x+u, y+v, rgbmap, palette);
algorithm.start(srcImage, dstImage);
if (algorithm.dimensions() == 1) {
const doc::LockImageBits<doc::RgbTraits> srcBits(srcImage);
doc::LockImageBits<doc::IndexedTraits> dstBits(dstImage);
auto srcIt = srcBits.begin();
auto dstIt = dstBits.begin();
for (int y=0; y<h; ++y) {
for (int x=0; x<w; ++x, ++srcIt, ++dstIt) {
ASSERT(srcIt != srcBits.end());
ASSERT(dstIt != dstBits.end());
*dstIt = algorithm.ditherRgbPixelToIndex(matrix, *srcIt, x, y, rgbmap, palette);
if (delegate) {
if (!delegate->continueTask())
return;
}
}
if (delegate) {
if (!delegate->continueTask())
return;
delegate->notifyTaskProgress(
double(y+1) / double(h));
}
}
}
else {
doc::LockImageBits<doc::IndexedTraits> dstBits(dstImage);
auto dstIt = dstBits.begin();
if (delegate) {
delegate->notifyTaskProgress(
double(y+1) / double(h));
for (int y=0; y<h; ++y) {
for (int x=0; x<w; ++x, ++dstIt) {
ASSERT(dstIt != dstBits.end());
*dstIt = algorithm.ditherRgbToIndex2D(x, y, rgbmap, palette);
if (delegate) {
if (!delegate->continueTask())
return;
}
}
if (delegate) {
delegate->notifyTaskProgress(
double(y+1) / double(h));
}
}
}
algorithm.finish();
}
} // namespace render

View File

@ -1,4 +1,5 @@
// Aseprite Render Library
// Copyright (c) 2019 Igara Studio S.A.
// Copyright (c) 2001-2017 David Capello
//
// This file is released under the terms of the MIT license.
@ -12,6 +13,8 @@
#include "doc/image_impl.h"
#include "doc/palette.h"
#include "doc/rgbmap.h"
#include "gfx/point.h"
#include "gfx/size.h"
#include "render/dithering_matrix.h"
#include "render/task_delegate.h"
@ -20,13 +23,26 @@ namespace render {
class DitheringAlgorithmBase {
public:
virtual ~DitheringAlgorithmBase() { }
virtual int dimensions() const { return 1; }
virtual void start(
const doc::Image* srcImage,
doc::Image* dstImage) { }
virtual void finish() { }
virtual doc::color_t ditherRgbPixelToIndex(
const DitheringMatrix& matrix,
const doc::color_t color,
const int x,
const int y,
const int x, const int y,
const doc::RgbMap* rgbmap,
const doc::Palette* palette) = 0;
const doc::Palette* palette) { return 0; }
virtual doc::color_t ditherRgbToIndex2D(
const int x, const int y,
const doc::RgbMap* rgbmap,
const doc::Palette* palette) { return 0; }
};
class OrderedDither : public DitheringAlgorithmBase {
@ -62,7 +78,6 @@ namespace render {
const DitheringMatrix& matrix,
const doc::Image* srcImage,
doc::Image* dstImage,
int u, int v,
const doc::RgbMap* rgbmap,
const doc::Palette* palette,
TaskDelegate* delegate = nullptr);

View File

@ -20,6 +20,7 @@
#include "doc/sprite.h"
#include "gfx/hsv.h"
#include "gfx/rgb.h"
#include "render/error_diffusion.h"
#include "render/ordered_dither.h"
#include "render/render.h"
#include "render/task_delegate.h"
@ -107,10 +108,13 @@ Image* convert_pixel_format(
case DitheringAlgorithm::Old:
dither.reset(new OrderedDither(is_background ? -1: new_mask_color));
break;
case DitheringAlgorithm::ErrorDiffusion:
dither.reset(new ErrorDiffusionDither(is_background ? -1: new_mask_color));
break;
}
if (dither)
dither_rgb_image_to_indexed(
*dither, ditheringMatrix, image, new_image, 0, 0, rgbmap, palette, delegate);
*dither, ditheringMatrix, image, new_image, rgbmap, palette, delegate);
return new_image;
}