mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-14 04:19:12 +00:00
Add Floyd-Steinberg dithering for RGBA -> Indexed conversion
This commit is contained in:
parent
c0b4224f32
commit
20b8ee0e57
@ -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
|
||||
|
@ -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 &&
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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"));
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
104
src/render/error_diffusion.cpp
Normal file
104
src/render/error_diffusion.cpp
Normal 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
|
41
src/render/error_diffusion.h
Normal file
41
src/render/error_diffusion.h
Normal 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
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user