1// Aseprite
2// Copyright (C) 2020 Igara Studio S.A.
3// Copyright (C) 2001-2017 David Capello
4//
5// This program is distributed under the terms of
6// the End-User License Agreement for Aseprite.
7
8#ifdef HAVE_CONFIG_H
9#include "config.h"
10#endif
11
12#include "filters/median_filter.h"
13
14#include "base/memory.h"
15#include "doc/image_impl.h"
16#include "doc/palette.h"
17#include "doc/rgbmap.h"
18#include "filters/filter_indexed_data.h"
19#include "filters/filter_manager.h"
20#include "filters/neighboring_pixels.h"
21#include "filters/tiled_mode.h"
22
23#include <algorithm>
24
25namespace filters {
26
27using namespace doc;
28
29namespace {
30 struct GetPixelsDelegateRgba {
31 std::vector<std::vector<uint8_t> >& channel;
32 int c;
33
34 GetPixelsDelegateRgba(std::vector<std::vector<uint8_t> >& channel) : channel(channel) { }
35
36 void reset() { c = 0; }
37
38 void operator()(RgbTraits::pixel_t color)
39 {
40 channel[0][c] = rgba_getr(color);
41 channel[1][c] = rgba_getg(color);
42 channel[2][c] = rgba_getb(color);
43 channel[3][c] = rgba_geta(color);
44 c++;
45 }
46 };
47
48 struct GetPixelsDelegateGrayscale {
49 std::vector<std::vector<uint8_t> >& channel;
50 int c;
51
52 GetPixelsDelegateGrayscale(std::vector<std::vector<uint8_t> >& channel) : channel(channel) { }
53
54 void reset() { c = 0; }
55
56 void operator()(GrayscaleTraits::pixel_t color)
57 {
58 channel[0][c] = graya_getv(color);
59 channel[1][c] = graya_geta(color);
60 c++;
61 }
62 };
63
64 struct GetPixelsDelegateIndexed {
65 const Palette* pal;
66 std::vector<std::vector<uint8_t> >& channel;
67 Target target;
68 int c;
69
70 GetPixelsDelegateIndexed(const Palette* pal, std::vector<std::vector<uint8_t> >& channel, Target target)
71 : pal(pal), channel(channel), target(target) { }
72
73 void reset() { c = 0; }
74
75 void operator()(IndexedTraits::pixel_t color)
76 {
77 if (target & TARGET_INDEX_CHANNEL) {
78 channel[0][c] = color;
79 }
80 else {
81 color_t rgb = pal->getEntry(color);
82 channel[0][c] = rgba_getr(rgb);
83 channel[1][c] = rgba_getg(rgb);
84 channel[2][c] = rgba_getb(rgb);
85 channel[3][c] = rgba_geta(rgb);
86 }
87 c++;
88 }
89 };
90};
91
92MedianFilter::MedianFilter()
93 : m_tiledMode(TiledMode::NONE)
94 , m_width(1)
95 , m_height(1)
96 , m_ncolors(0)
97 , m_channel(4)
98{
99}
100
101void MedianFilter::setTiledMode(TiledMode tiled)
102{
103 m_tiledMode = tiled;
104}
105
106void MedianFilter::setSize(int width, int height)
107{
108 ASSERT(width >= 1);
109 ASSERT(height >= 1);
110
111 m_width = std::max(1, width);
112 m_height = std::max(1, height);
113 m_ncolors = width*height;
114
115 for (int c = 0; c < 4; ++c)
116 m_channel[c].resize(m_ncolors);
117}
118
119const char* MedianFilter::getName()
120{
121 return "Median Blur";
122}
123
124void MedianFilter::applyToRgba(FilterManager* filterMgr)
125{
126 const Image* src = filterMgr->getSourceImage();
127 uint32_t* dst_address = (uint32_t*)filterMgr->getDestinationAddress();
128 Target target = filterMgr->getTarget();
129 int color;
130 int r, g, b, a;
131 GetPixelsDelegateRgba delegate(m_channel);
132 int x = filterMgr->x();
133 int x2 = x+filterMgr->getWidth();
134 int y = filterMgr->y();
135
136 for (; x<x2; ++x) {
137 // Avoid the non-selected region
138 if (filterMgr->skipPixel()) {
139 ++dst_address;
140 continue;
141 }
142
143 delegate.reset();
144 get_neighboring_pixels<RgbTraits>(src, x, y, m_width, m_height, m_width/2, m_height/2,
145 m_tiledMode, delegate);
146
147 color = get_pixel_fast<RgbTraits>(src, x, y);
148
149 if (target & TARGET_RED_CHANNEL) {
150 std::sort(m_channel[0].begin(), m_channel[0].end());
151 r = m_channel[0][m_ncolors/2];
152 }
153 else
154 r = rgba_getr(color);
155
156 if (target & TARGET_GREEN_CHANNEL) {
157 std::sort(m_channel[1].begin(), m_channel[1].end());
158 g = m_channel[1][m_ncolors/2];
159 }
160 else
161 g = rgba_getg(color);
162
163 if (target & TARGET_BLUE_CHANNEL) {
164 std::sort(m_channel[2].begin(), m_channel[2].end());
165 b = m_channel[2][m_ncolors/2];
166 }
167 else
168 b = rgba_getb(color);
169
170 if (target & TARGET_ALPHA_CHANNEL) {
171 std::sort(m_channel[3].begin(), m_channel[3].end());
172 a = m_channel[3][m_ncolors/2];
173 }
174 else
175 a = rgba_geta(color);
176
177 *(dst_address++) = rgba(r, g, b, a);
178 }
179}
180
181void MedianFilter::applyToGrayscale(FilterManager* filterMgr)
182{
183 const Image* src = filterMgr->getSourceImage();
184 uint16_t* dst_address = (uint16_t*)filterMgr->getDestinationAddress();
185 Target target = filterMgr->getTarget();
186 int color, k, a;
187 GetPixelsDelegateGrayscale delegate(m_channel);
188 int x = filterMgr->x();
189 int x2 = x+filterMgr->getWidth();
190 int y = filterMgr->y();
191
192 for (; x<x2; ++x) {
193 // Avoid the non-selected region
194 if (filterMgr->skipPixel()) {
195 ++dst_address;
196 continue;
197 }
198
199 delegate.reset();
200 get_neighboring_pixels<GrayscaleTraits>(src, x, y, m_width, m_height, m_width/2, m_height/2,
201 m_tiledMode, delegate);
202
203 color = get_pixel_fast<GrayscaleTraits>(src, x, y);
204
205 if (target & TARGET_GRAY_CHANNEL) {
206 std::sort(m_channel[0].begin(), m_channel[0].end());
207 k = m_channel[0][m_ncolors/2];
208 }
209 else
210 k = graya_getv(color);
211
212 if (target & TARGET_ALPHA_CHANNEL) {
213 std::sort(m_channel[1].begin(), m_channel[1].end());
214 a = m_channel[1][m_ncolors/2];
215 }
216 else
217 a = graya_geta(color);
218
219 *(dst_address++) = graya(k, a);
220 }
221}
222
223void MedianFilter::applyToIndexed(FilterManager* filterMgr)
224{
225 const Image* src = filterMgr->getSourceImage();
226 uint8_t* dst_address = (uint8_t*)filterMgr->getDestinationAddress();
227 const Palette* pal = filterMgr->getIndexedData()->getPalette();
228 const RgbMap* rgbmap = filterMgr->getIndexedData()->getRgbMap();
229 Target target = filterMgr->getTarget();
230 int color, r, g, b, a;
231 GetPixelsDelegateIndexed delegate(pal, m_channel, target);
232 int x = filterMgr->x();
233 int x2 = x+filterMgr->getWidth();
234 int y = filterMgr->y();
235
236 for (; x<x2; ++x) {
237 // Avoid the non-selected region
238 if (filterMgr->skipPixel()) {
239 ++dst_address;
240 continue;
241 }
242
243 delegate.reset();
244 get_neighboring_pixels<IndexedTraits>(src, x, y, m_width, m_height, m_width/2, m_height/2,
245 m_tiledMode, delegate);
246
247 if (target & TARGET_INDEX_CHANNEL) {
248 std::sort(m_channel[0].begin(), m_channel[0].end());
249 *(dst_address++) = m_channel[0][m_ncolors/2];
250 }
251 else {
252 color = get_pixel_fast<IndexedTraits>(src, x, y);
253 color = pal->getEntry(color);
254
255 if (target & TARGET_RED_CHANNEL) {
256 std::sort(m_channel[0].begin(), m_channel[0].end());
257 r = m_channel[0][m_ncolors/2];
258 }
259 else
260 r = rgba_getr(color);
261
262 if (target & TARGET_GREEN_CHANNEL) {
263 std::sort(m_channel[1].begin(), m_channel[1].end());
264 g = m_channel[1][m_ncolors/2];
265 }
266 else
267 g = rgba_getg(pal->getEntry(color));
268
269 if (target & TARGET_BLUE_CHANNEL) {
270 std::sort(m_channel[2].begin(), m_channel[2].end());
271 b = m_channel[2][m_ncolors/2];
272 }
273 else
274 b = rgba_getb(color);
275
276 if (target & TARGET_ALPHA_CHANNEL) {
277 std::sort(m_channel[3].begin(), m_channel[3].end());
278 a = m_channel[3][m_ncolors/2];
279 }
280 else
281 a = rgba_geta(color);
282
283 *(dst_address++) = rgbmap->mapColor(r, g, b, a);
284 }
285 }
286}
287
288} // namespace filters
289