| 2229 | } |
| 2230 | |
| 2231 | bool PathObject::simplify(PathObject** undo_duplicate, double threshold) |
| 2232 | { |
| 2233 | // A copy for reference and undo while this is modified. |
| 2234 | QScopedPointer<PathObject> original(new PathObject(*this)); |
| 2235 | |
| 2236 | // original_indices provides a mapping from this object's indices to the |
| 2237 | // equivalent original object indices in order to extract the relevant |
| 2238 | // parts of the original object later for cost calculation. |
| 2239 | // Note: curve handle indices may become incorrect, we don't need them. |
| 2240 | std::vector<MapCoordVector::size_type> original_indices; |
| 2241 | original_indices.resize(coords.size()); |
| 2242 | for (std::size_t i = 0, end = coords.size(); i < end; ++i) |
| 2243 | original_indices[i] = i; |
| 2244 | |
| 2245 | // A high value indicating an cost of deletion which is unknown. |
| 2246 | auto const undetermined_cost = std::numeric_limits<double>::max(); |
| 2247 | |
| 2248 | // This vector provides the costs of deleting individual nodes. |
| 2249 | std::vector<double> costs; |
| 2250 | costs.resize(coords.size(), undetermined_cost); |
| 2251 | |
| 2252 | // The empty LineSymbol will not generate any renderables, |
| 2253 | // thus reducing the cost of update(). |
| 2254 | // The temp object will be reused (but not reallocated) many times. |
| 2255 | LineSymbol empty_symbol; |
| 2256 | PathObject temp { &empty_symbol }; |
| 2257 | temp.coords.reserve(10); // enough for two bezier edges. |
| 2258 | |
| 2259 | for (auto part = path_parts.rbegin(); part != path_parts.rend(); ++part) |
| 2260 | { |
| 2261 | // Don't simplify parts which would get deleted. |
| 2262 | MapCoordVector::size_type minimum_part_size = part->isClosed() ? 4 : 3; |
| 2263 | auto minimumPartSizeReached = [&part, minimum_part_size]() -> bool |
| 2264 | { |
| 2265 | return (part->size() <= 7 && part->countRegularNodes() < minimum_part_size); |
| 2266 | }; |
| 2267 | |
| 2268 | auto minimum_cost_step = threshold / qMax(std::log2(part->size() / 64.0), 4.0); |
| 2269 | auto minimum_cost = 0.0; |
| 2270 | |
| 2271 | // Delete nodes in max n runs (where n = max. part.end_index - part.start_index) |
| 2272 | for (auto run = part->last_index; run > part->first_index; --run) |
| 2273 | { |
| 2274 | if (minimumPartSizeReached()) |
| 2275 | break; |
| 2276 | |
| 2277 | // Determine the costs of node deletion |
| 2278 | { |
| 2279 | auto extract_start = part->first_index; |
| 2280 | auto index = part->nextCoordIndex(extract_start); |
| 2281 | while (index < part->last_index) |
| 2282 | { |
| 2283 | auto extract_end = part->nextCoordIndex(index); |
| 2284 | if (costs[index] == undetermined_cost) |
| 2285 | { |
| 2286 | temp.assignCoordinates(*this, extract_start, extract_end); |
| 2287 | temp.deleteCoordinate(index - extract_start, true, Settings::DeleteBezierPoint_RetainExistingShape); |
| 2288 | // Debug check: start and end coords of the extracts should be at the same position |
no test coverage detected