1// Aseprite
2// Copyright (C) 2019-2022 Igara Studio S.A.
3// Copyright (C) 2001-2016 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/convolution_matrix_filter.h"
13
14#include "filters/convolution_matrix.h"
15#include "filters/filter_indexed_data.h"
16#include "filters/filter_manager.h"
17#include "filters/neighboring_pixels.h"
18#include "doc/image_impl.h"
19#include "doc/palette.h"
20#include "doc/rgbmap.h"
21
22namespace filters {
23
24using namespace doc;
25
26namespace {
27
28 struct GetPixelsDelegate {
29 int div;
30 const int* matrixData;
31
32 void reset(const ConvolutionMatrix* matrix) {
33 div = matrix->getDiv();
34 matrixData = &matrix->value(0, 0);
35 }
36 };
37
38 struct GetPixelsDelegateRgba : public GetPixelsDelegate {
39 int r, g, b, a;
40
41 void reset(const ConvolutionMatrix* matrix) {
42 GetPixelsDelegate::reset(matrix);
43 r = g = b = a = 0;
44 }
45
46 void operator()(RgbTraits::pixel_t color) {
47 if (*matrixData) {
48 if (rgba_geta(color) == 0)
49 div -= *matrixData;
50 else {
51 r += rgba_getr(color) * (*matrixData);
52 g += rgba_getg(color) * (*matrixData);
53 b += rgba_getb(color) * (*matrixData);
54 a += rgba_geta(color) * (*matrixData);
55 }
56 }
57 matrixData++;
58 }
59 };
60
61 struct GetPixelsDelegateGrayscale : public GetPixelsDelegate {
62 int v, a;
63
64 void reset(const ConvolutionMatrix* matrix) {
65 GetPixelsDelegate::reset(matrix);
66 v = a = 0;
67 }
68
69 void operator()(GrayscaleTraits::pixel_t color) {
70 if (*matrixData) {
71 if (graya_geta(color) == 0)
72 div -= *matrixData;
73 else {
74 v += graya_getv(color) * (*matrixData);
75 a += graya_geta(color) * (*matrixData);
76 }
77 }
78 matrixData++;
79 }
80 };
81
82 struct GetPixelsDelegateIndexed : public GetPixelsDelegate {
83 const Palette* pal;
84 int r, g, b, a, index;
85
86 GetPixelsDelegateIndexed(const Palette* pal) : pal(pal) { }
87
88 void reset(const ConvolutionMatrix* matrix) {
89 GetPixelsDelegate::reset(matrix);
90 r = g = b = a = index = 0;
91 }
92
93 void operator()(IndexedTraits::pixel_t color) {
94 if (*matrixData) {
95 index += color * (*matrixData);
96 color_t rgba = pal->getEntry(color);
97 if (rgba_geta(rgba) == 0)
98 div -= *matrixData;
99 else {
100 r += rgba_getr(rgba) * (*matrixData);
101 g += rgba_getg(rgba) * (*matrixData);
102 b += rgba_getb(rgba) * (*matrixData);
103 a += rgba_geta(rgba) * (*matrixData);
104 }
105 }
106 matrixData++;
107 }
108 };
109
110}
111
112ConvolutionMatrixFilter::ConvolutionMatrixFilter()
113 : m_matrix(NULL)
114 , m_tiledMode(TiledMode::NONE)
115{
116}
117
118void ConvolutionMatrixFilter::setMatrix(const std::shared_ptr<ConvolutionMatrix>& matrix)
119{
120 m_matrix = matrix;
121}
122
123void ConvolutionMatrixFilter::setTiledMode(TiledMode tiledMode)
124{
125 m_tiledMode = tiledMode;
126}
127
128const char* ConvolutionMatrixFilter::getName()
129{
130 return "Convolution Matrix";
131}
132
133void ConvolutionMatrixFilter::applyToRgba(FilterManager* filterMgr)
134{
135 if (!m_matrix)
136 return;
137
138 const Image* src = filterMgr->getSourceImage();
139 uint32_t* dst_address = (uint32_t*)filterMgr->getDestinationAddress();
140 Target target = filterMgr->getTarget();
141 uint32_t color;
142 GetPixelsDelegateRgba delegate;
143 int x = filterMgr->x();
144 int x2 = x+filterMgr->getWidth();
145 int y = filterMgr->y();
146
147 for (; x<x2; ++x) {
148 // Avoid the non-selected region
149 if (filterMgr->skipPixel()) {
150 ++dst_address;
151 continue;
152 }
153
154 delegate.reset(m_matrix.get());
155 get_neighboring_pixels<RgbTraits>(src, x, y,
156 m_matrix->getWidth(),
157 m_matrix->getHeight(),
158 m_matrix->getCenterX(),
159 m_matrix->getCenterY(),
160 m_tiledMode, delegate);
161
162 color = get_pixel_fast<RgbTraits>(src, x, y);
163 if (delegate.div == 0) {
164 *(dst_address++) = color;
165 continue;
166 }
167
168 if (target & TARGET_RED_CHANNEL) {
169 delegate.r = delegate.r / delegate.div + m_matrix->getBias();
170 delegate.r = std::clamp(delegate.r, 0, 255);
171 }
172 else
173 delegate.r = rgba_getr(color);
174
175 if (target & TARGET_GREEN_CHANNEL) {
176 delegate.g = delegate.g / delegate.div + m_matrix->getBias();
177 delegate.g = std::clamp(delegate.g, 0, 255);
178 }
179 else
180 delegate.g = rgba_getg(color);
181
182 if (target & TARGET_BLUE_CHANNEL) {
183 delegate.b = delegate.b / delegate.div + m_matrix->getBias();
184 delegate.b = std::clamp(delegate.b, 0, 255);
185 }
186 else
187 delegate.b = rgba_getb(color);
188
189 if (target & TARGET_ALPHA_CHANNEL) {
190 delegate.a = delegate.a / m_matrix->getDiv() + m_matrix->getBias();
191 delegate.a = std::clamp(delegate.a, 0, 255);
192 }
193 else
194 delegate.a = rgba_geta(color);
195
196 *(dst_address++) = rgba(delegate.r, delegate.g, delegate.b, delegate.a);
197 }
198}
199
200void ConvolutionMatrixFilter::applyToGrayscale(FilterManager* filterMgr)
201{
202 if (!m_matrix)
203 return;
204
205 const Image* src = filterMgr->getSourceImage();
206 uint16_t* dst_address = (uint16_t*)filterMgr->getDestinationAddress();
207 Target target = filterMgr->getTarget();
208 uint16_t color;
209 GetPixelsDelegateGrayscale delegate;
210 int x = filterMgr->x();
211 int x2 = x+filterMgr->getWidth();
212 int y = filterMgr->y();
213
214 for (; x<x2; ++x) {
215 // Avoid the non-selected region
216 if (filterMgr->skipPixel()) {
217 ++dst_address;
218 continue;
219 }
220
221 delegate.reset(m_matrix.get());
222 get_neighboring_pixels<GrayscaleTraits>(src, x, y,
223 m_matrix->getWidth(),
224 m_matrix->getHeight(),
225 m_matrix->getCenterX(),
226 m_matrix->getCenterY(),
227 m_tiledMode, delegate);
228
229 color = get_pixel_fast<GrayscaleTraits>(src, x, y);
230 if (delegate.div == 0) {
231 *(dst_address++) = color;
232 continue;
233 }
234
235 if (target & TARGET_GRAY_CHANNEL) {
236 delegate.v = delegate.v / delegate.div + m_matrix->getBias();
237 delegate.v = std::clamp(delegate.v, 0, 255);
238 }
239 else
240 delegate.v = graya_getv(color);
241
242 if (target & TARGET_ALPHA_CHANNEL) {
243 delegate.a = delegate.a / m_matrix->getDiv() + m_matrix->getBias();
244 delegate.a = std::clamp(delegate.a, 0, 255);
245 }
246 else
247 delegate.a = graya_geta(color);
248
249 *(dst_address++) = graya(delegate.v, delegate.a);
250 }
251}
252
253void ConvolutionMatrixFilter::applyToIndexed(FilterManager* filterMgr)
254{
255 if (!m_matrix)
256 return;
257
258 const Image* src = filterMgr->getSourceImage();
259 uint8_t* dst_address = (uint8_t*)filterMgr->getDestinationAddress();
260 const Palette* pal = filterMgr->getIndexedData()->getPalette();
261 const RgbMap* rgbmap = filterMgr->getIndexedData()->getRgbMap();
262 Target target = filterMgr->getTarget();
263 uint8_t color;
264 GetPixelsDelegateIndexed delegate(pal);
265 int x = filterMgr->x();
266 int x2 = x+filterMgr->getWidth();
267 int y = filterMgr->y();
268
269 for (; x<x2; ++x) {
270 // Avoid the non-selected region
271 if (filterMgr->skipPixel()) {
272 ++dst_address;
273 continue;
274 }
275
276 delegate.reset(m_matrix.get());
277 get_neighboring_pixels<IndexedTraits>(src, x, y,
278 m_matrix->getWidth(),
279 m_matrix->getHeight(),
280 m_matrix->getCenterX(),
281 m_matrix->getCenterY(),
282 m_tiledMode, delegate);
283
284 color = get_pixel_fast<IndexedTraits>(src, x, y);
285 if (delegate.div == 0) {
286 *(dst_address++) = color;
287 continue;
288 }
289
290 if (target & TARGET_INDEX_CHANNEL) {
291 delegate.index = delegate.index / m_matrix->getDiv() + m_matrix->getBias();
292 delegate.index = std::clamp(delegate.index, 0, 255);
293
294 *(dst_address++) = delegate.index;
295 }
296 else {
297 color = pal->getEntry(color);
298
299 if (target & TARGET_RED_CHANNEL) {
300 delegate.r = delegate.r / delegate.div + m_matrix->getBias();
301 delegate.r = std::clamp(delegate.r, 0, 255);
302 }
303 else
304 delegate.r = rgba_getr(color);
305
306 if (target & TARGET_GREEN_CHANNEL) {
307 delegate.g = delegate.g / delegate.div + m_matrix->getBias();
308 delegate.g = std::clamp(delegate.g, 0, 255);
309 }
310 else
311 delegate.g = rgba_getg(color);
312
313 if (target & TARGET_BLUE_CHANNEL) {
314 delegate.b = delegate.b / delegate.div + m_matrix->getBias();
315 delegate.b = std::clamp(delegate.b, 0, 255);
316 }
317 else
318 delegate.b = rgba_getb(color);
319
320 if (target & TARGET_ALPHA_CHANNEL) {
321 delegate.a = delegate.a / delegate.div + m_matrix->getBias();
322 delegate.a = std::clamp(delegate.a, 0, 255);
323 }
324 else
325 delegate.a = rgba_geta(color);
326
327 *(dst_address++) = rgbmap->mapColor(delegate.r, delegate.g, delegate.b, delegate.a);
328 }
329 }
330}
331
332} // namespace filters
333