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
22namespace filters {
23
24using namespace doc;
25
26namespace {
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
84OutlineFilter::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
93const char* OutlineFilter::getName()
94{
95 return "Outline";
96}
97
98void 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
139void 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
178void 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