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