/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2022 - Raw Material Software Limited JUCE is an open source library subject to commercial or open-source licensing. By using JUCE, you agree to the terms of both the JUCE 7 End-User License Agreement and JUCE Privacy Policy. End User License Agreement: www.juce.com/juce-7-licence Privacy Policy: www.juce.com/juce-privacy-policy Or: You may also use this code under the terms of the GPL v3 (see www.gnu.org/licenses). JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ namespace juce { JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6255 6263 6386) EdgeTable::EdgeTable (Rectangle area, const Path& path, const AffineTransform& transform) : bounds (area), // this is a very vague heuristic to make a rough guess at a good table size // for a given path, such that it's big enough to mostly avoid remapping, but also // not so big that it's wasteful for simple paths. maxEdgesPerLine (jmax (defaultEdgesPerLine / 2, 4 * (int) std::sqrt (path.data.size()))), lineStrideElements (maxEdgesPerLine * 2 + 1) { allocate(); int* t = table; for (int i = bounds.getHeight(); --i >= 0;) { *t = 0; t += lineStrideElements; } auto leftLimit = scale * bounds.getX(); auto topLimit = scale * bounds.getY(); auto rightLimit = scale * bounds.getRight(); auto heightLimit = scale * bounds.getHeight(); PathFlatteningIterator iter (path, transform); while (iter.next()) { auto y1 = roundToInt (iter.y1 * 256.0f); auto y2 = roundToInt (iter.y2 * 256.0f); if (y1 != y2) { y1 -= topLimit; y2 -= topLimit; auto startY = y1; int direction = -1; if (y1 > y2) { std::swap (y1, y2); direction = 1; } if (y1 < 0) y1 = 0; if (y2 > heightLimit) y2 = heightLimit; if (y1 < y2) { const double startX = 256.0f * iter.x1; const double multiplier = (iter.x2 - iter.x1) / (iter.y2 - iter.y1); auto stepSize = jlimit (1, 256, 256 / (1 + (int) std::abs (multiplier))); do { auto step = jmin (stepSize, y2 - y1, 256 - (y1 & 255)); auto x = roundToInt (startX + multiplier * ((y1 + (step >> 1)) - startY)); if (x < leftLimit) x = leftLimit; else if (x >= rightLimit) x = rightLimit - 1; addEdgePoint (x, y1 / scale, direction * step); y1 += step; } while (y1 < y2); } } } sanitiseLevels (path.isUsingNonZeroWinding()); } EdgeTable::EdgeTable (Rectangle rectangleToAdd) : bounds (rectangleToAdd), maxEdgesPerLine (defaultEdgesPerLine), lineStrideElements (defaultEdgesPerLine * 2 + 1) { allocate(); table[0] = 0; auto x1 = scale * rectangleToAdd.getX(); auto x2 = scale * rectangleToAdd.getRight(); int* t = table; for (int i = rectangleToAdd.getHeight(); --i >= 0;) { t[0] = 2; t[1] = x1; t[2] = 255; t[3] = x2; t[4] = 0; t += lineStrideElements; } } EdgeTable::EdgeTable (const RectangleList& rectanglesToAdd) : bounds (rectanglesToAdd.getBounds()), maxEdgesPerLine (defaultEdgesPerLine), lineStrideElements (defaultEdgesPerLine * 2 + 1), needToCheckEmptiness (true) { allocate(); clearLineSizes(); for (auto& r : rectanglesToAdd) { auto x1 = scale * r.getX(); auto x2 = scale * r.getRight(); auto y = r.getY() - bounds.getY(); for (int j = r.getHeight(); --j >= 0;) addEdgePointPair (x1, x2, y++, 255); } sanitiseLevels (true); } EdgeTable::EdgeTable (const RectangleList& rectanglesToAdd) : bounds (rectanglesToAdd.getBounds().getSmallestIntegerContainer()), maxEdgesPerLine (rectanglesToAdd.getNumRectangles() * 2), lineStrideElements (rectanglesToAdd.getNumRectangles() * 4 + 1) { bounds.setHeight (bounds.getHeight() + 1); allocate(); clearLineSizes(); for (auto& r : rectanglesToAdd) { auto x1 = roundToInt ((float) scale * r.getX()); auto x2 = roundToInt ((float) scale * r.getRight()); auto y1 = roundToInt ((float) scale * r.getY()) - (bounds.getY() * scale); auto y2 = roundToInt ((float) scale * r.getBottom()) - (bounds.getY() * scale); if (x2 <= x1 || y2 <= y1) continue; auto y = y1 / scale; auto lastLine = y2 / scale; if (y == lastLine) { addEdgePointPair (x1, x2, y, y2 - y1); } else { addEdgePointPair (x1, x2, y++, 255 - (y1 & 255)); while (y < lastLine) addEdgePointPair (x1, x2, y++, 255); jassert (y < bounds.getHeight()); addEdgePointPair (x1, x2, y, y2 & 255); } } sanitiseLevels (true); } EdgeTable::EdgeTable (Rectangle rectangleToAdd) : bounds ((int) std::floor (rectangleToAdd.getX()), roundToInt (rectangleToAdd.getY() * 256.0f) / scale, 2 + (int) rectangleToAdd.getWidth(), 2 + (int) rectangleToAdd.getHeight()), maxEdgesPerLine (defaultEdgesPerLine), lineStrideElements ((defaultEdgesPerLine * 2) + 1) { jassert (! rectangleToAdd.isEmpty()); allocate(); table[0] = 0; auto x1 = roundToInt ((float) scale * rectangleToAdd.getX()); auto x2 = roundToInt ((float) scale * rectangleToAdd.getRight()); auto y1 = roundToInt ((float) scale * rectangleToAdd.getY()) - (bounds.getY() * scale); auto y2 = roundToInt ((float) scale * rectangleToAdd.getBottom()) - (bounds.getY() * scale); jassert (y1 < 256); if (x2 <= x1 || y2 <= y1) { bounds.setHeight (0); return; } int lineY = 0; int* t = table; if ((y1 / scale) == (y2 / scale)) { t[0] = 2; t[1] = x1; t[2] = y2 - y1; t[3] = x2; t[4] = 0; ++lineY; t += lineStrideElements; } else { t[0] = 2; t[1] = x1; t[2] = 255 - (y1 & 255); t[3] = x2; t[4] = 0; ++lineY; t += lineStrideElements; while (lineY < (y2 / scale)) { t[0] = 2; t[1] = x1; t[2] = 255; t[3] = x2; t[4] = 0; ++lineY; t += lineStrideElements; } jassert (lineY < bounds.getHeight()); t[0] = 2; t[1] = x1; t[2] = y2 & 255; t[3] = x2; t[4] = 0; ++lineY; t += lineStrideElements; } while (lineY < bounds.getHeight()) { t[0] = 0; t += lineStrideElements; ++lineY; } } EdgeTable::EdgeTable (const EdgeTable& other) { operator= (other); } EdgeTable& EdgeTable::operator= (const EdgeTable& other) { bounds = other.bounds; maxEdgesPerLine = other.maxEdgesPerLine; lineStrideElements = other.lineStrideElements; needToCheckEmptiness = other.needToCheckEmptiness; allocate(); copyEdgeTableData (table, lineStrideElements, other.table, lineStrideElements, bounds.getHeight()); return *this; } EdgeTable::~EdgeTable() { } //============================================================================== static size_t getEdgeTableAllocationSize (int lineStride, int height) noexcept { // (leave an extra line at the end for use as scratch space) return (size_t) (lineStride * (2 + jmax (0, height))); } void EdgeTable::allocate() { table.malloc (getEdgeTableAllocationSize (lineStrideElements, bounds.getHeight())); } void EdgeTable::clearLineSizes() noexcept { int* t = table; for (int i = bounds.getHeight(); --i >= 0;) { *t = 0; t += lineStrideElements; } } void EdgeTable::copyEdgeTableData (int* dest, int destLineStride, const int* src, int srcLineStride, int numLines) noexcept { while (--numLines >= 0) { memcpy (dest, src, (size_t) (src[0] * 2 + 1) * sizeof (int)); src += srcLineStride; dest += destLineStride; } } void EdgeTable::sanitiseLevels (const bool useNonZeroWinding) noexcept { // Convert the table from relative windings to absolute levels.. int* lineStart = table; for (int y = bounds.getHeight(); --y >= 0;) { auto num = lineStart[0]; if (num > 0) { auto* items = reinterpret_cast (lineStart + 1); auto* itemsEnd = items + num; // sort the X coords std::sort (items, itemsEnd); auto* src = items; auto correctedNum = num; int level = 0; while (src < itemsEnd) { level += src->level; auto x = src->x; ++src; while (src < itemsEnd && src->x == x) { level += src->level; ++src; --correctedNum; } auto corrected = std::abs (level); if (corrected / scale) { if (useNonZeroWinding) { corrected = 255; } else { corrected &= 511; if (corrected / scale) corrected = 511 - corrected; } } items->x = x; items->level = corrected; ++items; } lineStart[0] = correctedNum; (items - 1)->level = 0; // force the last level to 0, just in case something went wrong in creating the table } lineStart += lineStrideElements; } } void EdgeTable::remapTableForNumEdges (const int newNumEdgesPerLine) { if (newNumEdgesPerLine != maxEdgesPerLine) { maxEdgesPerLine = newNumEdgesPerLine; jassert (bounds.getHeight() > 0); auto newLineStrideElements = maxEdgesPerLine * 2 + 1; HeapBlock newTable (getEdgeTableAllocationSize (newLineStrideElements, bounds.getHeight())); copyEdgeTableData (newTable, newLineStrideElements, table, lineStrideElements, bounds.getHeight()); table.swapWith (newTable); lineStrideElements = newLineStrideElements; } } inline void EdgeTable::remapWithExtraSpace (int numPoints) { remapTableForNumEdges (numPoints * 2); jassert (numPoints < maxEdgesPerLine); } void EdgeTable::optimiseTable() { int maxLineElements = 0; for (int i = bounds.getHeight(); --i >= 0;) maxLineElements = jmax (maxLineElements, table[i * lineStrideElements]); remapTableForNumEdges (maxLineElements); } void EdgeTable::addEdgePoint (const int x, const int y, const int winding) { jassert (y >= 0 && y < bounds.getHeight()); auto* line = table + lineStrideElements * y; auto numPoints = line[0]; if (numPoints >= maxEdgesPerLine) { remapWithExtraSpace (numPoints); line = table + lineStrideElements * y; } line[0] = numPoints + 1; line += numPoints * 2; line[1] = x; line[2] = winding; } void EdgeTable::addEdgePointPair (int x1, int x2, int y, int winding) { jassert (y >= 0 && y < bounds.getHeight()); auto* line = table + lineStrideElements * y; auto numPoints = line[0]; if (numPoints + 1 >= maxEdgesPerLine) { remapWithExtraSpace (numPoints + 1); line = table + lineStrideElements * y; } line[0] = numPoints + 2; line += numPoints * 2; line[1] = x1; line[2] = winding; line[3] = x2; line[4] = -winding; } void EdgeTable::translate (float dx, int dy) noexcept { bounds.translate ((int) std::floor (dx), dy); int* lineStart = table; auto intDx = (int) (dx * 256.0f); for (int i = bounds.getHeight(); --i >= 0;) { auto* line = lineStart; lineStart += lineStrideElements; auto num = *line++; while (--num >= 0) { *line += intDx; line += 2; } } } void EdgeTable::multiplyLevels (float amount) { int* lineStart = table; auto multiplier = (int) (amount * 256.0f); for (int y = 0; y < bounds.getHeight(); ++y) { auto numPoints = lineStart[0]; auto* item = reinterpret_cast (lineStart + 1); lineStart += lineStrideElements; while (--numPoints > 0) { item->level = jmin (255, (item->level * multiplier) / scale); ++item; } } } void EdgeTable::intersectWithEdgeTableLine (const int y, const int* const otherLine) { jassert (y >= 0 && y < bounds.getHeight()); auto* srcLine = table + lineStrideElements * y; auto srcNum1 = *srcLine; if (srcNum1 == 0) return; auto srcNum2 = *otherLine; if (srcNum2 == 0) { *srcLine = 0; return; } auto right = bounds.getRight() * scale; // optimise for the common case where our line lies entirely within a // single pair of points, as happens when clipping to a simple rect. if (srcNum2 == 2 && otherLine[2] >= 255) { clipEdgeTableLineToRange (srcLine, otherLine[1], jmin (right, otherLine[3])); return; } bool isUsingTempSpace = false; const int* src1 = srcLine + 1; auto x1 = *src1++; const int* src2 = otherLine + 1; auto x2 = *src2++; int destIndex = 0, destTotal = 0; int level1 = 0, level2 = 0; int lastX = std::numeric_limits::min(), lastLevel = 0; while (srcNum1 > 0 && srcNum2 > 0) { int nextX; if (x1 <= x2) { if (x1 == x2) { level2 = *src2++; x2 = *src2++; --srcNum2; } nextX = x1; level1 = *src1++; x1 = *src1++; --srcNum1; } else { nextX = x2; level2 = *src2++; x2 = *src2++; --srcNum2; } if (nextX > lastX) { if (nextX >= right) break; lastX = nextX; auto nextLevel = (level1 * (level2 + 1)) / scale; jassert (isPositiveAndBelow (nextLevel, 256)); if (nextLevel != lastLevel) { if (destTotal >= maxEdgesPerLine) { srcLine[0] = destTotal; if (isUsingTempSpace) { auto tempSize = (size_t) srcNum1 * 2 * sizeof (int); auto oldTemp = static_cast (alloca (tempSize)); memcpy (oldTemp, src1, tempSize); remapTableForNumEdges (jmax (256, destTotal * 2)); srcLine = table + lineStrideElements * y; auto* newTemp = table + lineStrideElements * bounds.getHeight(); memcpy (newTemp, oldTemp, tempSize); src1 = newTemp; } else { remapTableForNumEdges (jmax (256, destTotal * 2)); srcLine = table + lineStrideElements * y; } } ++destTotal; lastLevel = nextLevel; if (! isUsingTempSpace) { isUsingTempSpace = true; auto* temp = table + lineStrideElements * bounds.getHeight(); memcpy (temp, src1, (size_t) srcNum1 * 2 * sizeof (int)); src1 = temp; } srcLine[++destIndex] = nextX; srcLine[++destIndex] = nextLevel; } } } if (lastLevel > 0) { if (destTotal >= maxEdgesPerLine) { srcLine[0] = destTotal; remapTableForNumEdges (jmax (256, destTotal * 2)); srcLine = table + lineStrideElements * y; } ++destTotal; srcLine[++destIndex] = right; srcLine[++destIndex] = 0; } srcLine[0] = destTotal; } void EdgeTable::clipEdgeTableLineToRange (int* dest, const int x1, const int x2) noexcept { int* lastItem = dest + (dest[0] * 2 - 1); if (x2 < lastItem[0]) { if (x2 <= dest[1]) { dest[0] = 0; return; } while (x2 < lastItem[-2]) { --(dest[0]); lastItem -= 2; } lastItem[0] = x2; lastItem[1] = 0; } if (x1 > dest[1]) { while (lastItem[0] > x1) lastItem -= 2; auto itemsRemoved = (int) (lastItem - (dest + 1)) / 2; if (itemsRemoved > 0) { dest[0] -= itemsRemoved; memmove (dest + 1, lastItem, (size_t) dest[0] * (sizeof (int) * 2)); } dest[1] = x1; } } //============================================================================== void EdgeTable::clipToRectangle (Rectangle r) { auto clipped = r.getIntersection (bounds); if (clipped.isEmpty()) { needToCheckEmptiness = false; bounds.setHeight (0); } else { auto top = clipped.getY() - bounds.getY(); auto bottom = clipped.getBottom() - bounds.getY(); if (bottom < bounds.getHeight()) bounds.setHeight (bottom); for (int i = 0; i < top; ++i) table[lineStrideElements * i] = 0; if (clipped.getX() > bounds.getX() || clipped.getRight() < bounds.getRight()) { auto x1 = scale * clipped.getX(); auto x2 = scale * jmin (bounds.getRight(), clipped.getRight()); int* line = table + lineStrideElements * top; for (int i = bottom - top; --i >= 0;) { if (line[0] != 0) clipEdgeTableLineToRange (line, x1, x2); line += lineStrideElements; } } needToCheckEmptiness = true; } } void EdgeTable::excludeRectangle (Rectangle r) { auto clipped = r.getIntersection (bounds); if (! clipped.isEmpty()) { auto top = clipped.getY() - bounds.getY(); auto bottom = clipped.getBottom() - bounds.getY(); const int rectLine[] = { 4, std::numeric_limits::min(), 255, scale * clipped.getX(), 0, scale * clipped.getRight(), 255, std::numeric_limits::max(), 0 }; for (int i = top; i < bottom; ++i) intersectWithEdgeTableLine (i, rectLine); needToCheckEmptiness = true; } } void EdgeTable::clipToEdgeTable (const EdgeTable& other) { auto clipped = other.bounds.getIntersection (bounds); if (clipped.isEmpty()) { needToCheckEmptiness = false; bounds.setHeight (0); } else { auto top = clipped.getY() - bounds.getY(); auto bottom = clipped.getBottom() - bounds.getY(); if (bottom < bounds.getHeight()) bounds.setHeight (bottom); if (clipped.getRight() < bounds.getRight()) bounds.setRight (clipped.getRight()); for (int i = 0; i < top; ++i) table[lineStrideElements * i] = 0; auto* otherLine = other.table + other.lineStrideElements * (clipped.getY() - other.bounds.getY()); for (int i = top; i < bottom; ++i) { intersectWithEdgeTableLine (i, otherLine); otherLine += other.lineStrideElements; } needToCheckEmptiness = true; } } void EdgeTable::clipLineToMask (int x, int y, const uint8* mask, int maskStride, int numPixels) { y -= bounds.getY(); if (y < 0 || y >= bounds.getHeight()) return; needToCheckEmptiness = true; if (numPixels <= 0) { table[lineStrideElements * y] = 0; return; } auto* tempLine = static_cast (alloca ((size_t) (numPixels * 2 + 4) * sizeof (int))); int destIndex = 0, lastLevel = 0; while (--numPixels >= 0) { auto alpha = *mask; mask += maskStride; if (alpha != lastLevel) { tempLine[++destIndex] = (x * scale); tempLine[++destIndex] = alpha; lastLevel = alpha; } ++x; } if (lastLevel > 0) { tempLine[++destIndex] = (x * scale); tempLine[++destIndex] = 0; } tempLine[0] = destIndex >> 1; intersectWithEdgeTableLine (y, tempLine); } bool EdgeTable::isEmpty() noexcept { if (needToCheckEmptiness) { needToCheckEmptiness = false; int* t = table; for (int i = bounds.getHeight(); --i >= 0;) { if (t[0] > 1) return false; t += lineStrideElements; } bounds.setHeight (0); } return bounds.getHeight() == 0; } JUCE_END_IGNORE_WARNINGS_MSVC } // namespace juce