#include "ScanlineMaskConverter.h" #include "EllipsePlotter.h" #include "Rect2i.h" #include "ScanlineMask.h" #include "Vec2i.h" #include "LinePlotter.h" #include "ScanlineMaskBuilder.h" #include "IPlotter.h" #include "PLCore.h" #include #include #define PL_SCANLINE_MASKS_DEBUGGING 0 #if PL_SCANLINE_MASKS_DEBUGGING #include "stb_image_write.h" #endif // The algorithm that we're trying to emulate consistently paints on covered pixels of a line as in-bounds, // while also identifying which pixels are part of the poly interior. // // We use a plotting algorithm to generate an outline with some rules: // - If a plot moves vertically in the same direction as the previous vertical movement, then the inversion bit for the pixel is flipped. // - A scanline is then run across the row, toggling state between inversion points, and filling any that contain a plot point. namespace PortabilityLayer { enum PlotterVertical { PlotterVertical_NegY, PlotterVertical_PosY, PlotterVertical_None, }; static void FillPresenceFlag(uint8_t *bitfield, size_t element) { #if PL_SCANLINE_MASKS_DEBUGGING bitfield[element * 4] = 255; #else bitfield[element / 4] |= 1 << ((element & 3) * 2); #endif } static bool ReadPresenceFlag(const uint8_t *bitfield, size_t element) { #if PL_SCANLINE_MASKS_DEBUGGING return bitfield[element * 4] == 255;; #else return (bitfield[element / 4] & (1 << ((element & 3) * 2))) != 0; #endif } static void FlipBorderFlag(uint8_t *bitfield, size_t element) { #if PL_SCANLINE_MASKS_DEBUGGING bitfield[element * 4 + 1] ^= 255; #else bitfield[element / 4] ^= 2 << ((element & 3) * 2); #endif } static bool ReadBorderFlag(const uint8_t *bitfield, size_t element) { #if PL_SCANLINE_MASKS_DEBUGGING return bitfield[element * 4 + 1] == 255; #else return (bitfield[element / 4] & (2 << ((element & 3) * 2))) != 0; #endif } static PlotterVertical VerticalForPlotDir(PlotDirection plotDir) { switch (plotDir) { case PlotDirection_NegX_NegY: case PlotDirection_0X_NegY: case PlotDirection_PosX_NegY: return PlotterVertical_NegY; case PlotDirection_NegX_PosY: case PlotDirection_0X_PosY: case PlotDirection_PosX_PosY: return PlotterVertical_PosY; default: return PlotterVertical_None; } } static bool FlushScanline(const uint8_t *flagBits, size_t firstElement, size_t width, ScanlineMaskBuilder &maskBuilder) { size_t spanStart = 0; bool isBorderToggleActive = false; bool maskSpanState = false; for (size_t col = 0; col < width; col++) { const size_t element = firstElement + col; const bool presenceFlag = ReadPresenceFlag(flagBits, element); const bool borderFlag = ReadBorderFlag(flagBits, element); if (borderFlag) isBorderToggleActive = !isBorderToggleActive; const bool elementState = (isBorderToggleActive || presenceFlag); if (elementState != maskSpanState) { if (!maskBuilder.AppendSpan(col - spanStart)) return false; spanStart = col; maskSpanState = elementState; } } if (!maskBuilder.AppendSpan(width - spanStart)) return false; return true; } ScanlineMask *ComputePlot(uint32_t width, uint32_t height, const Vec2i &minPoint, IPlotter &plotter) { assert(width > 0 && height > 0); if (static_cast(width) * static_cast(height) > SIZE_MAX) return nullptr; const size_t numElements = width * height; #if PL_SCANLINE_MASKS_DEBUGGING const size_t storageSize = numElements * 4; #else const size_t storageSize = (numElements * 2 + 7) / 8; #endif void *storage = NewPtr(storageSize); if (!storage) return nullptr; uint8_t *flagBits = static_cast(storage); memset(flagBits, 0, storageSize); #if PL_SCANLINE_MASKS_DEBUGGING { for (size_t i = 0; i < storageSize; i += 4) flagBits[i + 3] = 255; } #endif const Vec2i initialPoint = plotter.GetPoint() - minPoint; bool haveFirstVertical = false; size_t firstVerticalElement = 0; // First element that contains a vertical movement on the first row PlotterVertical firstVertical = PlotterVertical::PlotterVertical_None; PlotterVertical lastVertical = PlotterVertical::PlotterVertical_None; size_t currentElement = static_cast(initialPoint.m_y) * width + static_cast(initialPoint.m_x); FillPresenceFlag(flagBits, currentElement); for (;;) { const size_t prevElement = currentElement; const PlotDirection plotDir = plotter.PlotNext(); if (plotDir == PlotDirection_Exhausted) break; switch (plotDir) { case PlotDirection_NegX_NegY: currentElement = currentElement - 1 - width; break; case PlotDirection_0X_NegY: currentElement = currentElement - width; break; case PlotDirection_PosX_NegY: currentElement = currentElement + 1 - width; break; case PlotDirection_NegX_PosY: currentElement = currentElement + width - 1; break; case PlotDirection_0X_PosY: currentElement = currentElement + width; break; case PlotDirection_PosX_PosY: currentElement = currentElement + width + 1; break; case PlotDirection_NegX_0Y: currentElement = currentElement - 1; break; case PlotDirection_PosX_0Y: currentElement = currentElement + 1; break; default: break; } FillPresenceFlag(flagBits, currentElement); #if PL_SCANLINE_MASKS_DEBUGGING const Vec2i expectedPoint = plotter.GetPoint() - minPoint; const int64_t expectedElement = static_cast(expectedPoint.m_y) * width + expectedPoint.m_x; assert(currentElement == expectedElement); #endif assert(currentElement < numElements); const PlotterVertical verticalForPlotDir = VerticalForPlotDir(plotDir); if (verticalForPlotDir != PlotterVertical_None) { if (!haveFirstVertical) { haveFirstVertical = true; firstVerticalElement = prevElement; firstVertical = verticalForPlotDir; } if (verticalForPlotDir == lastVertical) FlipBorderFlag(flagBits, prevElement); lastVertical = verticalForPlotDir; } } if (lastVertical == firstVertical) FlipBorderFlag(flagBits, firstVerticalElement); assert(plotter.GetPoint() - minPoint == initialPoint); ScanlineMaskBuilder maskBuilder; bool failed = false; for (size_t rowFirstElement = 0; rowFirstElement < numElements; rowFirstElement += width) { if (!FlushScanline(flagBits, rowFirstElement, width, maskBuilder)) { failed = true; break; } } #if PL_SCANLINE_MASKS_DEBUGGING static int debugID = 0; char path[256]; sprintf_s(path, "DebugData/ScanlineMask%i.png", debugID++); stbi_write_png(path, width, height, 4, flagBits, width * 4); #endif DisposePtr(storage); return ScanlineMask::Create(Rect::Create(minPoint.m_y, minPoint.m_x, minPoint.m_y + static_cast(height), minPoint.m_x + static_cast(width)), maskBuilder); } class SinglePointPlotter final : public IPlotter { public: explicit SinglePointPlotter(const Vec2i &point); PlotDirection PlotNext() override; const Vec2i &GetPoint() const override; private: const Vec2i &m_point; }; SinglePointPlotter::SinglePointPlotter(const Vec2i &point) : m_point(point) { } PlotDirection SinglePointPlotter::PlotNext() { return PlotDirection_Exhausted; } const Vec2i &SinglePointPlotter::GetPoint() const { return m_point; } class PolyPlotter final : public IPlotter { public: PolyPlotter(const Vec2i *points, size_t numPoints); PlotDirection PlotNext() override; const Vec2i &GetPoint() const override; private: LinePlotter m_linePlotter; const Vec2i *m_points; size_t m_currentTargetPoint; size_t m_numPoints; }; PolyPlotter::PolyPlotter(const Vec2i *points, size_t numPoints) : m_points(points) , m_currentTargetPoint(0) , m_numPoints(numPoints) { m_linePlotter.Reset(points[numPoints - 1], points[0]); } PlotDirection PolyPlotter::PlotNext() { if (m_currentTargetPoint == m_numPoints) return PlotDirection_Exhausted; for (;;) { const PlotDirection plotDir = m_linePlotter.PlotNext(); if (plotDir == PlotDirection_Exhausted) { m_currentTargetPoint++; if (m_currentTargetPoint == m_numPoints) return PlotDirection_Exhausted; m_linePlotter.Reset(m_points[m_currentTargetPoint - 1], m_points[m_currentTargetPoint]); } else return plotDir; } } const Vec2i &PolyPlotter::GetPoint() const { return m_linePlotter.GetPoint(); } ScanlineMask *ScanlineMaskConverter::CompilePoly(const Vec2i *points, size_t numPoints) { assert(numPoints > 0); Vec2i minPoint = points[0]; Vec2i maxPoint = points[0]; for (size_t i = 1; i < numPoints; i++) { minPoint.m_x = std::min(minPoint.m_x, points[i].m_x); minPoint.m_y = std::min(minPoint.m_y, points[i].m_y); maxPoint.m_x = std::max(maxPoint.m_x, points[i].m_x); maxPoint.m_y = std::max(maxPoint.m_y, points[i].m_y); } const uint32_t width = static_cast(maxPoint.m_x - minPoint.m_x) + 1; const uint32_t height = static_cast(maxPoint.m_y - minPoint.m_y) + 1; PolyPlotter polyPlotter(points, numPoints); return ComputePlot(width, height, minPoint, polyPlotter); } ScanlineMask *ScanlineMaskConverter::CompileEllipse(const Rect2i &rect) { if (!rect.IsValid() || rect.m_topLeft.m_x == rect.m_bottomRight.m_x || rect.m_topLeft.m_y == rect.m_bottomRight.m_y) return nullptr; const uint32_t width = rect.m_bottomRight.m_x - rect.m_topLeft.m_x; const uint32_t height = rect.m_bottomRight.m_y - rect.m_topLeft.m_y; if (width == 1 || height == 1) { if (width == 1 && height == 1) { SinglePointPlotter plotter(rect.m_topLeft); return ComputePlot(1, 1, rect.m_topLeft, plotter); } else { LinePlotter plotter; plotter.Reset(rect.m_topLeft, rect.m_bottomRight - Vec2i(1, 1)); return ComputePlot(width, height, rect.m_topLeft, plotter); } } else { EllipsePlotter plotter; plotter.Reset(rect); return ComputePlot(width, height, rect.m_topLeft, plotter); } } }