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 | |
22 | namespace filters { |
23 | |
24 | using namespace doc; |
25 | |
26 | namespace { |
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 | |
112 | ConvolutionMatrixFilter::ConvolutionMatrixFilter() |
113 | : m_matrix(NULL) |
114 | , m_tiledMode(TiledMode::NONE) |
115 | { |
116 | } |
117 | |
118 | void ConvolutionMatrixFilter::setMatrix(const std::shared_ptr<ConvolutionMatrix>& matrix) |
119 | { |
120 | m_matrix = matrix; |
121 | } |
122 | |
123 | void ConvolutionMatrixFilter::setTiledMode(TiledMode tiledMode) |
124 | { |
125 | m_tiledMode = tiledMode; |
126 | } |
127 | |
128 | const char* ConvolutionMatrixFilter::getName() |
129 | { |
130 | return "Convolution Matrix" ; |
131 | } |
132 | |
133 | void 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 | |
200 | void 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 | |
253 | void 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 | |