| 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 | |