1 | // Aseprite |
2 | // Copyright (C) 2019 Igara Studio S.A. |
3 | // |
4 | // This program is distributed under the terms of |
5 | // the End-User License Agreement for Aseprite. |
6 | |
7 | #ifdef HAVE_CONFIG_H |
8 | #include "config.h" |
9 | #endif |
10 | |
11 | #include "filters/outline_filter.h" |
12 | |
13 | #include "doc/image.h" |
14 | #include "doc/palette.h" |
15 | #include "doc/rgbmap.h" |
16 | #include "filters/filter_indexed_data.h" |
17 | #include "filters/filter_manager.h" |
18 | #include "filters/neighboring_pixels.h" |
19 | |
20 | #include <algorithm> |
21 | |
22 | namespace filters { |
23 | |
24 | using namespace doc; |
25 | |
26 | namespace { |
27 | |
28 | struct GetPixelsDelegate { |
29 | color_t bgColor; |
30 | int transparent; // Transparent pixels |
31 | int opaque; // Opaque pixels |
32 | int matrix; |
33 | int bit; |
34 | |
35 | void init(const color_t bgColor, |
36 | const OutlineFilter::Matrix matrix) { |
37 | this->bgColor = bgColor; |
38 | this->matrix = (int)matrix; |
39 | } |
40 | |
41 | void reset() { |
42 | transparent = opaque = 0; |
43 | bit = 1; |
44 | } |
45 | }; |
46 | |
47 | struct GetPixelsDelegateRgba : public GetPixelsDelegate { |
48 | void operator()(RgbTraits::pixel_t color) { |
49 | if (rgba_geta(color) == 0 || color == bgColor) |
50 | transparent += (matrix & bit ? 1: 0); |
51 | else |
52 | opaque += (matrix & bit ? 1: 0); |
53 | bit <<= 1; |
54 | } |
55 | }; |
56 | |
57 | struct GetPixelsDelegateGrayscale : public GetPixelsDelegate { |
58 | void operator()(GrayscaleTraits::pixel_t color) { |
59 | if (graya_geta(color) == 0 || color == bgColor) |
60 | transparent += (matrix & bit ? 1: 0); |
61 | else |
62 | opaque += (matrix & bit ? 1: 0); |
63 | bit <<= 1; |
64 | } |
65 | }; |
66 | |
67 | struct GetPixelsDelegateIndexed : public GetPixelsDelegate { |
68 | const Palette* pal; |
69 | |
70 | GetPixelsDelegateIndexed(const Palette* pal) : pal(pal) { } |
71 | |
72 | void operator()(IndexedTraits::pixel_t color) { |
73 | color_t rgba = pal->getEntry(color); |
74 | if (rgba_geta(rgba) == 0 || color == bgColor) |
75 | transparent += (matrix & bit ? 1: 0); |
76 | else |
77 | opaque += (matrix & bit ? 1: 0); |
78 | bit <<= 1; |
79 | } |
80 | }; |
81 | |
82 | } |
83 | |
84 | OutlineFilter::OutlineFilter() |
85 | : m_place(Place::Outside) |
86 | , m_matrix(Matrix::Circle) |
87 | , m_tiledMode(TiledMode::NONE) |
88 | , m_color(0) |
89 | , m_bgColor(0) |
90 | { |
91 | } |
92 | |
93 | const char* OutlineFilter::getName() |
94 | { |
95 | return "Outline" ; |
96 | } |
97 | |
98 | void OutlineFilter::applyToRgba(FilterManager* filterMgr) |
99 | { |
100 | const Image* src = filterMgr->getSourceImage(); |
101 | const uint32_t* src_address = (uint32_t*)filterMgr->getSourceAddress(); |
102 | uint32_t* dst_address = (uint32_t*)filterMgr->getDestinationAddress(); |
103 | int x = filterMgr->x(); |
104 | const int x2 = x+filterMgr->getWidth(); |
105 | const int y = filterMgr->y(); |
106 | Target target = filterMgr->getTarget(); |
107 | int r, g, b, a, n; |
108 | color_t c; |
109 | bool isTransparent; |
110 | |
111 | GetPixelsDelegateRgba delegate; |
112 | delegate.init(m_bgColor, m_matrix); |
113 | |
114 | for (; x<x2; ++x, ++src_address, ++dst_address) { |
115 | if (filterMgr->skipPixel()) |
116 | continue; |
117 | |
118 | delegate.reset(); |
119 | get_neighboring_pixels<RgbTraits>(src, x, y, 3, 3, 1, 1, m_tiledMode, delegate); |
120 | |
121 | c = *src_address; |
122 | n = (m_place == Place::Outside ? delegate.opaque: delegate.transparent); |
123 | isTransparent = (rgba_geta(c) == 0 || c == m_bgColor); |
124 | |
125 | if ((n >= 1) && |
126 | ((m_place == Place::Outside && isTransparent) || |
127 | (m_place == Place::Inside && !isTransparent))) { |
128 | r = (target & TARGET_RED_CHANNEL ? rgba_getr(m_color): rgba_getr(c)); |
129 | g = (target & TARGET_GREEN_CHANNEL ? rgba_getg(m_color): rgba_getg(c)); |
130 | b = (target & TARGET_BLUE_CHANNEL ? rgba_getb(m_color): rgba_getb(c)); |
131 | a = (target & TARGET_ALPHA_CHANNEL ? rgba_geta(m_color): rgba_geta(c)); |
132 | c = rgba(r, g, b, a); |
133 | } |
134 | |
135 | *dst_address = c; |
136 | } |
137 | } |
138 | |
139 | void OutlineFilter::applyToGrayscale(FilterManager* filterMgr) |
140 | { |
141 | const Image* src = filterMgr->getSourceImage(); |
142 | const uint16_t* src_address = (uint16_t*)filterMgr->getSourceAddress(); |
143 | uint16_t* dst_address = (uint16_t*)filterMgr->getDestinationAddress(); |
144 | int x = filterMgr->x(); |
145 | const int x2 = x+filterMgr->getWidth(); |
146 | const int y = filterMgr->y(); |
147 | Target target = filterMgr->getTarget(); |
148 | int k, a, n; |
149 | color_t c; |
150 | bool isTransparent; |
151 | |
152 | GetPixelsDelegateGrayscale delegate; |
153 | delegate.init(m_bgColor, m_matrix); |
154 | |
155 | for (; x<x2; ++x, ++src_address, ++dst_address) { |
156 | if (filterMgr->skipPixel()) |
157 | continue; |
158 | |
159 | delegate.reset(); |
160 | get_neighboring_pixels<GrayscaleTraits>(src, x, y, 3, 3, 1, 1, m_tiledMode, delegate); |
161 | |
162 | c = *src_address; |
163 | n = (m_place == Place::Outside ? delegate.opaque: delegate.transparent); |
164 | isTransparent = (graya_geta(c) == 0 || c == m_bgColor); |
165 | |
166 | if ((n >= 1) && |
167 | ((m_place == Place::Outside && isTransparent) || |
168 | (m_place == Place::Inside && !isTransparent))) { |
169 | k = (target & TARGET_GRAY_CHANNEL ? graya_getv(m_color): graya_getv(c)); |
170 | a = (target & TARGET_ALPHA_CHANNEL ? graya_geta(m_color): graya_geta(c)); |
171 | c = graya(k, a); |
172 | } |
173 | |
174 | *dst_address = c; |
175 | } |
176 | } |
177 | |
178 | void OutlineFilter::applyToIndexed(FilterManager* filterMgr) |
179 | { |
180 | const Image* src = filterMgr->getSourceImage(); |
181 | const uint8_t* src_address = (uint8_t*)filterMgr->getSourceAddress(); |
182 | uint8_t* dst_address = (uint8_t*)filterMgr->getDestinationAddress(); |
183 | const Palette* pal = filterMgr->getIndexedData()->getPalette(); |
184 | const RgbMap* rgbmap = filterMgr->getIndexedData()->getRgbMap(); |
185 | int x = filterMgr->x(); |
186 | const int x2 = x+filterMgr->getWidth(); |
187 | const int y = filterMgr->y(); |
188 | Target target = filterMgr->getTarget(); |
189 | int r, g, b, a, n; |
190 | color_t c; |
191 | bool isTransparent; |
192 | |
193 | GetPixelsDelegateIndexed delegate(pal); |
194 | delegate.init(m_bgColor, m_matrix); |
195 | |
196 | for (; x<x2; ++x, ++src_address, ++dst_address) { |
197 | if (filterMgr->skipPixel()) |
198 | continue; |
199 | |
200 | delegate.reset(); |
201 | get_neighboring_pixels<IndexedTraits>(src, x, y, 3, 3, 1, 1, m_tiledMode, delegate); |
202 | |
203 | c = *src_address; |
204 | n = (m_place == Place::Outside ? delegate.opaque: delegate.transparent); |
205 | |
206 | if (target & TARGET_INDEX_CHANNEL) { |
207 | isTransparent = (c == m_bgColor); |
208 | } |
209 | else { |
210 | isTransparent = (rgba_geta(pal->getEntry(c)) == 0 || c == m_bgColor); |
211 | } |
212 | |
213 | if ((n >= 1) && |
214 | ((m_place == Place::Outside && isTransparent) || |
215 | (m_place == Place::Inside && !isTransparent))) { |
216 | if (target & TARGET_INDEX_CHANNEL) { |
217 | c = m_color; |
218 | } |
219 | else { |
220 | c = pal->getEntry(c); |
221 | r = (target & TARGET_RED_CHANNEL ? rgba_getr(m_color): rgba_getr(c)); |
222 | g = (target & TARGET_GREEN_CHANNEL ? rgba_getg(m_color): rgba_getg(c)); |
223 | b = (target & TARGET_BLUE_CHANNEL ? rgba_getb(m_color): rgba_getb(c)); |
224 | a = (target & TARGET_ALPHA_CHANNEL ? rgba_geta(m_color): rgba_geta(c)); |
225 | c = rgbmap->mapColor(r, g, b, a); |
226 | } |
227 | } |
228 | |
229 | *dst_address = c; |
230 | } |
231 | } |
232 | |
233 | } // namespace filters |
234 | |