| 1 | // Aseprite Document Library |
| 2 | // Copyright (c) 2019-2020 Igara Studio S.A. |
| 3 | // Copyright (c) 2001-2014 David Capello |
| 4 | // |
| 5 | // This file is released under the terms of the MIT license. |
| 6 | // Read LICENSE.txt for more information. |
| 7 | |
| 8 | #ifdef HAVE_CONFIG_H |
| 9 | #include "config.h" |
| 10 | #endif |
| 11 | |
| 12 | #include "base/debug.h" |
| 13 | #include "doc/algo.h" |
| 14 | #include "doc/algorithm/polygon.h" |
| 15 | |
| 16 | #include "gfx/point.h" |
| 17 | |
| 18 | #include <algorithm> |
| 19 | #include <vector> |
| 20 | |
| 21 | namespace doc { |
| 22 | |
| 23 | static void addPointsWithoutDuplicatingLastOne(int x, int y, std::vector<gfx::Point>* pts) |
| 24 | { |
| 25 | const gfx::Point newPoint(x, y); |
| 26 | if (pts->empty() || |
| 27 | *(pts->end()-1) != newPoint) { |
| 28 | pts->push_back(newPoint); |
| 29 | } |
| 30 | } |
| 31 | |
| 32 | // createUnion() joins a single scan point "x" (a pixel in other words) |
| 33 | // to the input vector "pairs". |
| 34 | // Each pair "pairs[i], pairs[i+1]" is a representation |
| 35 | // of a horizontal scan segment "i". |
| 36 | // An added scan point "x" to the "pairs" vector is represented |
| 37 | // by two consecutive values of "x" if it is an insolated point. |
| 38 | // If "x" is between or next to a scan segment, this function creates an |
| 39 | // union, making a fusion between "x" <-> "pairs[i], pairs[i+1]". |
| 40 | // Additionally, after the union step, this function handles |
| 41 | // overlapped situations respect to the nexts scan segments "i+2", |
| 42 | // "i+4", etc. |
| 43 | // Note: "pairs" must be sorted prior execution of this function. |
| 44 | bool algorithm::createUnion(std::vector<int>& pairs, |
| 45 | const int x, |
| 46 | int& ints) |
| 47 | { |
| 48 | if (ints == 0) { |
| 49 | pairs.insert(pairs.begin(), 2, x); |
| 50 | ints = 2; |
| 51 | return true; |
| 52 | } |
| 53 | else if (pairs.size() < ints || ints == 1) { |
| 54 | // Error |
| 55 | return false; |
| 56 | } |
| 57 | else if (ints%2 == 1) |
| 58 | ints--; |
| 59 | |
| 60 | for (int i=0; i < ints; i+=2) { |
| 61 | // Case: pairs[i] pairs[i+1] |
| 62 | // O --------- O |
| 63 | // -x- |
| 64 | if (x == pairs[i] - 1) { |
| 65 | pairs[i] = x; |
| 66 | return true; |
| 67 | } |
| 68 | // Case: pairs[i] pairs[i+1] |
| 69 | // O --------- O |
| 70 | // -x- |
| 71 | else if (x < pairs[i] - 1) { |
| 72 | pairs.insert(pairs.begin() + i, 2, x); |
| 73 | ints += 2; |
| 74 | return true; |
| 75 | } |
| 76 | // Case: pairs[i] pairs[i+1] |
| 77 | // O --------- O |
| 78 | // -x- |
| 79 | // or -x- |
| 80 | else if (x == pairs[i+1] + 1 || x == pairs[i+1]) { |
| 81 | pairs[i+1] = x; |
| 82 | while (ints > i+2 && pairs[i+2] <= x+1) { |
| 83 | pairs.erase(pairs.begin() + (i+1)); |
| 84 | pairs.erase(pairs.begin() + (i+1)); |
| 85 | ints -= 2; |
| 86 | if (i+2 >= pairs.size()) |
| 87 | break; |
| 88 | } |
| 89 | return true; |
| 90 | } |
| 91 | // Case: pairs[i] pairs[i+1] |
| 92 | // O --------- O |
| 93 | // -x- |
| 94 | else if (x >= pairs[i] && x < pairs[i+1]) |
| 95 | return true; |
| 96 | } |
| 97 | // Case: pairs[i] pairs[i+1] |
| 98 | // O --------- O |
| 99 | // -x- |
| 100 | if (x > pairs[ints-1]) { |
| 101 | pairs.insert(pairs.begin() + ints, 2, x); |
| 102 | ints += 2; |
| 103 | return true; |
| 104 | } |
| 105 | return false; |
| 106 | } |
| 107 | |
| 108 | void algorithm::polygon(int vertices, const int* points, void* data, AlgoHLine proc) |
| 109 | { |
| 110 | if (!vertices) |
| 111 | return; |
| 112 | |
| 113 | // We load locally the points to "verts" and remove the duplicate points |
| 114 | // to manage easily the input vector, by the way, we find |
| 115 | // "ymin" and "ymax" to use them later like vertical scan limits |
| 116 | // on the scan line loop. |
| 117 | int ymin = *(points+1); |
| 118 | int ymax = *(points+1); |
| 119 | std::vector<gfx::Point> verts(1); |
| 120 | verts[0].x = *points; |
| 121 | verts[0].y = *(points+1); |
| 122 | int verticesAux = vertices; |
| 123 | for (int i=2; i < vertices*2; i+=2) { |
| 124 | int last = verts.size() - 1; |
| 125 | if (verts[last].x == *(points+i) && verts[last].y == *(points + (i+1))) { |
| 126 | verticesAux--; |
| 127 | continue; |
| 128 | } |
| 129 | verts.push_back(gfx::Point(*(points + i), *(points + (i+1)))); |
| 130 | ASSERT(last + 1 == verts.size() - 1); |
| 131 | if (verts[last+1].y < ymin) |
| 132 | ymin = verts[last+1].y; |
| 133 | if (verts[last+1].y > ymax) |
| 134 | ymax = verts[last+1].y; |
| 135 | } |
| 136 | vertices = verticesAux; |
| 137 | |
| 138 | std::vector<gfx::Point> pts; |
| 139 | for (int c=0; c < verts.size(); ++c) { |
| 140 | if (c == verts.size() - 1) { |
| 141 | algo_line_continuous(verts[verts.size()-1].x, |
| 142 | verts[verts.size()-1].y, |
| 143 | verts[0].x, |
| 144 | verts[0].y, |
| 145 | (void*)&pts, |
| 146 | (AlgoPixel)&addPointsWithoutDuplicatingLastOne); |
| 147 | // Consideration when we want to draw a simple pixel with contour tool |
| 148 | // dragging the cursor inside of a pixel (in this case pts contains |
| 149 | // just one element, which want to preserve). |
| 150 | if (pts.size() > 1) |
| 151 | // We remove the last point which is a duplicate point of |
| 152 | // the "pts" first element. |
| 153 | pts.pop_back(); |
| 154 | } |
| 155 | else { |
| 156 | algo_line_continuous(verts[c].x, |
| 157 | verts[c].y, |
| 158 | verts[c+1].x, |
| 159 | verts[c+1].y, |
| 160 | (void*)&pts, |
| 161 | (AlgoPixel)&addPointsWithoutDuplicatingLastOne); |
| 162 | } |
| 163 | } |
| 164 | |
| 165 | // Scan Line Loop: |
| 166 | int y; |
| 167 | int x1, y1; |
| 168 | int x2, y2; |
| 169 | int ind1, ind2; |
| 170 | int ints; |
| 171 | std::vector<int> polyInts(pts.size()); |
| 172 | for (y = ymin; y <= ymax; y++) { |
| 173 | ints = 0; |
| 174 | for (int i=0; i < pts.size(); i++) { |
| 175 | if (!i) { |
| 176 | ind1 = pts.size() - 1; |
| 177 | ind2 = 0; |
| 178 | } |
| 179 | else { |
| 180 | ind1 = i - 1; |
| 181 | ind2 = i; |
| 182 | } |
| 183 | y1 = pts[ind1].y; |
| 184 | y2 = pts[ind2].y; |
| 185 | if (y1 < y2) { |
| 186 | x1 = pts[ind1].x; |
| 187 | x2 = pts[ind2].x; |
| 188 | } |
| 189 | else if (y1 > y2) { |
| 190 | y2 = pts[ind1].y; |
| 191 | y1 = pts[ind2].y; |
| 192 | x2 = pts[ind1].x; |
| 193 | x1 = pts[ind2].x; |
| 194 | } |
| 195 | else |
| 196 | continue; |
| 197 | |
| 198 | if ((y >= y1 && y < y2) || |
| 199 | (y == ymax && y > y1 && y <= y2)) { |
| 200 | polyInts[ints] = (int) ((float)((y - y1)*(x2 - x1)) / (float)(y2 - y1) + 0.5f + (float)x1); |
| 201 | ints++; |
| 202 | } |
| 203 | } |
| 204 | |
| 205 | std::sort(polyInts.begin(), polyInts.begin() + ints); |
| 206 | |
| 207 | for (int i=0; i < pts.size(); i++) { |
| 208 | if (pts[i].y == y) |
| 209 | createUnion(polyInts, pts[i].x, ints); |
| 210 | } |
| 211 | |
| 212 | for (int i=0; i < ints; i+=2) |
| 213 | proc(polyInts[i], y, polyInts[i+1], data); |
| 214 | } |
| 215 | } |
| 216 | |
| 217 | } // namespace doc |
| 218 | |