1// Aseprite Render Library
2// Copyright (c) 2019-2022 Igara Studio S.A
3// Copyright (c) 2017 David Capello
4//
5// This file is released under the terms of the MIT license.
6// Read LICENSE.txt for more information.
7
8#ifdef HAVE_CONFIG_H
9#include "config.h"
10#endif
11
12#include "render/error_diffusion.h"
13
14#include "gfx/hsl.h"
15#include "gfx/rgb.h"
16
17#include <algorithm>
18
19namespace render {
20
21ErrorDiffusionDither::ErrorDiffusionDither(int transparentIndex)
22 : m_transparentIndex(transparentIndex)
23{
24}
25
26void ErrorDiffusionDither::start(
27 const doc::Image* srcImage,
28 doc::Image* dstImage,
29 const double factor)
30{
31 m_srcImage = srcImage;
32 m_width = 2+srcImage->width();
33 for (int i=0; i<kChannels; ++i)
34 m_err[i].resize(m_width*2, 0);
35 m_lastY = -1;
36 m_factor = int(factor * 100.0);
37}
38
39void ErrorDiffusionDither::finish()
40{
41}
42
43doc::color_t ErrorDiffusionDither::ditherRgbToIndex2D(
44 const int x, const int y,
45 const doc::RgbMap* rgbmap,
46 const doc::Palette* palette)
47{
48 if (y != m_lastY) {
49 for (int i=0; i<kChannels; ++i) {
50 int* row0 = &m_err[i][0];
51 int* row1 = row0 + m_width;
52 int* end1 = row1 + m_width;
53 std::copy(row1, end1, row0);
54 std::fill(row1, end1, 0);
55 }
56 m_lastY = y;
57 }
58
59 doc::color_t color =
60 doc::get_pixel_fast<doc::RgbTraits>(m_srcImage, x, y);
61
62 // Get RGB values + quatization error
63 int v[kChannels] = {
64 doc::rgba_getr(color),
65 doc::rgba_getg(color),
66 doc::rgba_getb(color),
67 doc::rgba_geta(color)
68 };
69 for (int i=0; i<kChannels; ++i) {
70 v[i] += m_err[i][x+1];
71 v[i] = std::clamp(v[i], 0, 255);
72 }
73
74 const doc::color_t index =
75 (rgbmap ? rgbmap->mapColor(v[0], v[1], v[2], v[3]):
76 palette->findBestfit(v[0], v[1], v[2], v[3], m_transparentIndex));
77
78 doc::color_t palColor = palette->getEntry(index);
79 if (m_transparentIndex == index || doc::rgba_geta(palColor) == 0) {
80 // "color" without alpha
81 palColor = (color & doc::rgba_rgb_mask);
82 }
83
84 const int quantError[kChannels] = {
85 v[0] - doc::rgba_getr(palColor),
86 v[1] - doc::rgba_getg(palColor),
87 v[2] - doc::rgba_getb(palColor),
88 v[3] - doc::rgba_geta(palColor)
89 };
90
91 // TODO using Floyd-Steinberg matrix here but it should be configurable
92 for (int i=0; i<kChannels; ++i) {
93 int* err = &m_err[i][x];
94 const int q = quantError[i] * m_factor / 100;
95 const int a = q * 7 / 16;
96 const int b = q * 3 / 16;
97 const int c = q * 5 / 16;
98 const int d = q * 1 / 16;
99
100 if (y & 1) {
101 err[0 ] += a;
102 err[m_width+2] += b;
103 err[m_width+1] += c;
104 err[m_width ] += d;
105 }
106 else {
107 err[ +2] += a;
108 err[m_width ] += b;
109 err[m_width+1] += c;
110 err[m_width+2] += d;
111 }
112 }
113
114 return index;
115}
116
117} // namespace render
118