From 27fb7324188ba5dc45106a8025b8caffa476eb6e Mon Sep 17 00:00:00 2001 From: Mikhail Romanko Date: Sun, 13 Sep 2026 22:31:21 +0300 Subject: [PATCH] Initial commit --- .github/workflows/ci.yaml | 89 ++++++++++++ .gitignore | 68 +++++++++ Bezier.c | 167 +++++++++++++++++++++ CMakeLists.txt | 26 ++++ CgeScanline.h | 111 ++++++++++++++ LICENSE | 12 ++ Makefile.lint | 29 ++++ Makefile.mingw | 23 +++ Makefile.posix | 33 +++++ Makefile.win32 | 23 +++ README.md | 8 ++ Scanline.c | 296 ++++++++++++++++++++++++++++++++++++++ 12 files changed, 885 insertions(+) create mode 100644 .github/workflows/ci.yaml create mode 100644 .gitignore create mode 100644 Bezier.c create mode 100644 CMakeLists.txt create mode 100644 CgeScanline.h create mode 100644 LICENSE create mode 100644 Makefile.lint create mode 100644 Makefile.mingw create mode 100644 Makefile.posix create mode 100644 Makefile.win32 create mode 100644 README.md create mode 100644 Scanline.c diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..01e9e41 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,89 @@ +name: CI + +on: + push: + branches: [trunk] + pull_request: + branches: [trunk] + +jobs: + build-and-analyze: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install tools + run: | + sudo apt-get update -qq + sudo apt-get install -y build-essential clang clang-tools clang-tidy cppcheck gcc-mingw-w64-x86-64 cmake + + - name: Build with GCC + run: | + make -f Makefile.posix clean + make -f Makefile.posix CC=gcc CFLAGS="-std=c99 -Wall -Wextra -Wpedantic -Werror -O2" + + - name: Build with Clang + run: | + make -f Makefile.posix clean + make -f Makefile.posix CC=clang CFLAGS="-std=c99 -Wall -Wextra -Wpedantic -Werror -O2" + + - name: Build with MinGW + run: | + make -f Makefile.posix clean + make -f Makefile.mingw CC=x86_64-w64-mingw32-gcc AR=x86_64-w64-mingw32-ar CFLAGS="-std=c99 -Wall -Wextra -Wpedantic -Werror -O2" + + - name: Build with CMake + run: | + cmake -S . -B build + cmake --build build + + - name: Linting checks + run: | + echo "Running all linting checks..." + FAILED=0 + + # cppcheck + echo "=== Check 1/4: cppcheck ===" + if make -f Makefile.lint cppcheck; then + echo "cppcheck: PASSED" + else + echo "cppcheck: FAILED" + FAILED=1 + fi + + # clang-tidy + echo "=== Check 2/4: clang-tidy ===" + if make -f Makefile.lint clang-tidy; then + echo "clang-tidy: PASSED" + else + echo "clang-tidy: FAILED" + FAILED=1 + fi + + # scan-build + echo "=== Check 3/4: scan-build ===" + if make -f Makefile.lint scan-build; then + echo "scan-build: PASSED" + else + echo "scan-build: FAILED" + FAILED=1 + fi + + # security-check + echo "=== Check 4/4: security-check ===" + if make -f Makefile.lint security-check; then + echo "security-check: PASSED" + else + echo "security-check: FAILED" + FAILED=1 + fi + + # Final result + if [ $FAILED -ne 0 ]; then + echo "One or more linting checks failed." + exit 1 + else + echo "All linting checks passed." + fi \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f765426 --- /dev/null +++ b/.gitignore @@ -0,0 +1,68 @@ +# ---> C +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf + +# ---> CMake +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps +CMakeUserPresets.json + diff --git a/Bezier.c b/Bezier.c new file mode 100644 index 0000000..b6227c7 --- /dev/null +++ b/Bezier.c @@ -0,0 +1,167 @@ +#include "CgeScanline.h" + +#define BEZIER_MAX_DEPTH 64 + +typedef struct { + CgeScanlineEdge *edge; + size_t edgeSize; + size_t count; +} EdgeAddContext; + +static void edgeAddLineTo(uint32_t x0, uint32_t y0, uint32_t x1, uint32_t y1, + void *userData) { + EdgeAddContext *ctx = (EdgeAddContext *)userData; + + if (ctx->count >= ctx->edgeSize) + return; + ctx->count += CgeScanlineEdgeAdd(ctx->edge + ctx->count, x0, y0, x1, y1); +} + +static void addVec(uint32_t a[2], uint32_t b[2], uint32_t c[2]) { + c[0] = a[0] + b[0]; + c[1] = a[1] + b[1]; +} + +static void shiftVec(uint32_t a[2], uint32_t b[2], int val) { + b[0] = a[0] >> val; + b[1] = a[1] >> val; +} + +static uint64_t distSq(uint32_t x, uint32_t y) { + if (x & 0x80000000) + x = -x; + if (y & 0x80000000) + y = -y; + + return ((uint64_t)x * x + (uint64_t)y * y); +} + +static uint64_t flatnessCubic(uint32_t p[4][2]) { + uint64_t f1, f2; + f1 = distSq(p[0][0] - (p[1][0] << 1) + p[2][0], + p[0][1] - (p[1][1] << 1) + p[2][1]); + f2 = distSq(p[1][0] - (p[2][0] << 1) + p[3][0], + p[1][1] - (p[2][1] << 1) + p[3][1]); + return f1 > f2 ? f1 : f2; +} + +static uint64_t flatnessQuad(uint32_t p[3][2]) { + return distSq(p[0][0] - (p[1][0] << 1) + p[2][0], + p[0][1] - (p[1][1] << 1) + p[2][1]); +} + +static void bezierCubicFlattenRec(uint32_t x0, uint32_t y0, uint32_t x1, + uint32_t y1, uint32_t x2, uint32_t y2, + uint32_t x3, uint32_t y3, uint64_t flatnessSq, + int depth, CgeScanlineLineToCallback lineTo, + void *userData) { + uint32_t a[4][2] = {{x0, y0}, {x1, y1}, {x2, y2}, {x3, y3}}; + uint32_t b[3][2]; + uint32_t c[2][2]; + uint32_t d[2]; + + if (depth < BEZIER_MAX_DEPTH && flatnessCubic(a) > flatnessSq) { + addVec(a[0], a[1], b[0]); + addVec(a[1], a[2], b[1]); + addVec(a[2], a[3], b[2]); + addVec(b[0], b[1], c[0]); + addVec(b[1], b[2], c[1]); + addVec(c[0], c[1], d); + shiftVec(b[0], b[0], 1); + shiftVec(b[1], b[1], 1); + shiftVec(b[2], b[2], 1); + shiftVec(c[0], c[0], 2); + shiftVec(c[1], c[1], 2); + shiftVec(d, d, 3); + + bezierCubicFlattenRec(a[0][0], a[0][1], b[0][0], b[0][1], c[0][0], + c[0][1], d[0], d[1], flatnessSq, depth + 1, + lineTo, userData); + + bezierCubicFlattenRec(d[0], d[1], c[1][0], c[1][1], b[2][0], b[2][1], + a[3][0], a[3][1], flatnessSq, depth + 1, + lineTo, userData); + } else { + lineTo(x0, y0, x3, y3, userData); + } +} + +static void bezierQuadFlattenRec(uint32_t x0, uint32_t y0, uint32_t x1, + uint32_t y1, uint32_t x2, uint32_t y2, + uint64_t flatnessSq, int depth, + CgeScanlineLineToCallback lineTo, + void *userData) { + uint32_t a[3][2] = {{x0, y0}, {x1, y1}, {x2, y2}}; + uint32_t b[2][2]; + uint32_t c[2]; + + if (depth < BEZIER_MAX_DEPTH && flatnessQuad(a) > flatnessSq) { + addVec(a[0], a[1], b[0]); + addVec(a[1], a[2], b[1]); + addVec(b[0], b[1], c); + shiftVec(b[0], b[0], 1); + shiftVec(b[1], b[1], 1); + shiftVec(c, c, 2); + + bezierQuadFlattenRec(a[0][0], a[0][1], b[0][0], b[0][1], c[0], c[1], + flatnessSq, depth + 1, lineTo, userData); + bezierQuadFlattenRec(c[0], c[1], b[1][0], b[1][1], a[2][0], a[2][1], + flatnessSq, depth + 1, lineTo, userData); + } else { + lineTo(x0, y0, x2, y2, userData); + } +} + +void CgeScanlineBezierCubicFlatten(uint32_t x0, uint32_t y0, uint32_t x1, + uint32_t y1, uint32_t x2, uint32_t y2, + uint32_t x3, uint32_t y3, uint32_t flatness, + CgeScanlineLineToCallback lineTo, + void *userData) { + uint64_t flatnessSq = (uint64_t)flatness * (uint64_t)flatness; + if (flatnessSq < 1) + flatnessSq = 1; + bezierCubicFlattenRec(x0, y0, x1, y1, x2, y2, x3, y3, + flatnessSq, 0, lineTo, userData); +} + +void CgeScanlineBezierQuadFlatten(uint32_t x0, uint32_t y0, uint32_t x1, + uint32_t y1, uint32_t x2, uint32_t y2, + uint32_t flatness, + CgeScanlineLineToCallback lineTo, + void *userData) { + uint64_t flatnessSq = (uint64_t)flatness * (uint64_t)flatness; + if (flatnessSq < 1) + flatnessSq = 1; + bezierQuadFlattenRec(x0, y0, x1, y1, x2, y2, + flatnessSq, 0, lineTo, userData); +} + +size_t CgeScanlineBezierCubicEdges(CgeScanlineEdge *edge, size_t edgeSize, + uint32_t x0, uint32_t y0, uint32_t x1, + uint32_t y1, uint32_t x2, uint32_t y2, + uint32_t x3, uint32_t y3, + uint32_t flatness) { + EdgeAddContext ctx; + ctx.edge = edge; + ctx.edgeSize = edgeSize; + ctx.count = 0; + + CgeScanlineBezierCubicFlatten(x0, y0, x1, y1, x2, y2, x3, y3, flatness, + edgeAddLineTo, &ctx); + + return ctx.count; +} + +size_t CgeScanlineBezierQuadEdges(CgeScanlineEdge *edge, size_t edgeSize, + uint32_t x0, uint32_t y0, uint32_t x1, + uint32_t y1, uint32_t x2, uint32_t y2, + uint32_t flatness) { + EdgeAddContext ctx; + ctx.edge = edge; + ctx.edgeSize = edgeSize; + ctx.count = 0; + + CgeScanlineBezierQuadFlatten(x0, y0, x1, y1, x2, y2, flatness, + edgeAddLineTo, &ctx); + return ctx.count; +} diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..247031a --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.10) +project(CgeScanline LANGUAGES C) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) + +set(SOURCES + Scanline.c + Bezier.c +) + +set(HEADERS + CgeScanline.h +) + +add_library(CgeScanline STATIC ${SOURCES} ${HEADERS}) + +target_include_directories(CgeScanline PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +install(TARGETS CgeScanline + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin +) + +install(FILES ${HEADERS} DESTINATION include) diff --git a/CgeScanline.h b/CgeScanline.h new file mode 100644 index 0000000..ca39fad --- /dev/null +++ b/CgeScanline.h @@ -0,0 +1,111 @@ +#ifndef CGE_SCANLINE_H +#define CGE_SCANLINE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +enum CgeScanlineFillRule { + CGE_SCANLINE_ODD_EVEN = 0, + CGE_SCANLINE_NON_ZERO = (1 << 0), +}; + +enum CgeScanlineCode { + CGE_SCANLINE_EOK, + CGE_SCANLINE_EDONE, + CGE_SCANLINE_EAET, + CGE_SCANLINE_ESPANS, +}; + +typedef struct CgeScanlineEdge { + uint32_t startX; + uint32_t startY; + uint32_t endY; + uint32_t step; + uint32_t error; + uint32_t threshold; + uint32_t dir; + int winding; +} CgeScanlineEdge; + +typedef struct CgeScanlineEdgeTable { + size_t index; + uint32_t x; + uint32_t error; +} CgeScanlineEdgeTable; + +typedef struct CgeScanlineSpan { + uint32_t start; + uint32_t end; +} CgeScanlineSpan; + +typedef void (*CgeScanlineRowCallback)(uint32_t y, CgeScanlineSpan *spans, + size_t size, void *userData); + +typedef void (*CgeScanlineLineToCallback)(uint32_t x0, uint32_t y0, + uint32_t x1, uint32_t y1, + void *userData); + +typedef struct CgeScanlineState { + CgeScanlineEdge *edge; + size_t edgeSize; + CgeScanlineEdgeTable *aet; + size_t aetCapacity; + CgeScanlineSpan *span; + size_t spanCapacity; + CgeScanlineRowCallback callback; + void *userData; + int flags; + + /* Internal state - do not edit */ + uint32_t yCurrent; + uint32_t yMax; + size_t aetSize; + size_t edgeCursor; +} CgeScanlineState; + +int CgeScanlineStateInit(CgeScanlineState *state, CgeScanlineEdge *edge, + size_t edgeSize, CgeScanlineEdgeTable *aet, + size_t aetCapacity, CgeScanlineSpan *span, + size_t spanCapacity, CgeScanlineRowCallback callback, + void *userData, int flags); + +int CgeScanlineStateNext(CgeScanlineState *state, int *result); + +int CgeScanlineEdgeAdd(CgeScanlineEdge *edge, uint32_t aX, uint32_t aY, + uint32_t bX, uint32_t bY); + +int CgeScanlineSpans(CgeScanlineEdge *edge, size_t edgeSize, + CgeScanlineRowCallback callback, void *userData, + int flags); + +void CgeScanlineBezierCubicFlatten(uint32_t x0, uint32_t y0, uint32_t x1, + uint32_t y1, uint32_t x2, uint32_t y2, + uint32_t x3, uint32_t y3, uint32_t flatness, + CgeScanlineLineToCallback lineTo, + void *userData); + +void CgeScanlineBezierQuadFlatten(uint32_t x0, uint32_t y0, uint32_t x1, + uint32_t y1, uint32_t x2, uint32_t y2, + uint32_t flatness, + CgeScanlineLineToCallback lineTo, + void *userData); + +size_t CgeScanlineBezierCubicEdges(CgeScanlineEdge *edge, size_t edgeSize, + uint32_t x0, uint32_t y0, uint32_t x1, + uint32_t y1, uint32_t x2, uint32_t y2, + uint32_t x3, uint32_t y3, uint32_t flatness); + +size_t CgeScanlineBezierQuadEdges(CgeScanlineEdge *edge, size_t edgeSize, + uint32_t x0, uint32_t y0, uint32_t x1, + uint32_t y1, uint32_t x2, uint32_t y2, + uint32_t flatness); + +#ifdef __cplusplus +} +#endif + +#endif /* CGE_SCANLINE_H */ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3d44ffd --- /dev/null +++ b/LICENSE @@ -0,0 +1,12 @@ +Copyright (C) 2026 by blankhex me@blankhex.com + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. diff --git a/Makefile.lint b/Makefile.lint new file mode 100644 index 0000000..3897d63 --- /dev/null +++ b/Makefile.lint @@ -0,0 +1,29 @@ +# Makefile.lint - Individual linting targets + +.PHONY: all +all: cppcheck clang-tidy scan-build security-check + @echo "All linting scheduled" + +.PHONY: cppcheck +cppcheck: + @echo "Running cppcheck..." + @cppcheck --enable=warning,performance,portability --std=c99 --quiet -Iinclude -I. . + +.PHONY: clang-tidy +clang-tidy: + @echo "Running clang-tidy..." + @for file in $$(find . -name "*.c" -type f); do \ + clang-tidy "$$file" -- -I. -Iinclude -std=c99; \ + done + +.PHONY: scan-build +scan-build: + @echo "Running scan-build..." + @scan-build --status-bugs make -f Makefile.posix clean all CC=clang + +.PHONY: security-check +security-check: + @echo "Scanning for unsafe C functions..." + @grep -rnE '\b(strcpy|strcat|sprintf|gets|scanf|sscanf|realpath|mktemp|tempnam|tmpnam|getwd|getlogin)\b' \ + . --include="*.c" --include="*.h" | grep -v "^Binary file" && \ + exit 1 || exit 0 \ No newline at end of file diff --git a/Makefile.mingw b/Makefile.mingw new file mode 100644 index 0000000..834819c --- /dev/null +++ b/Makefile.mingw @@ -0,0 +1,23 @@ +# MinGW Makefile for CgeScanline + +CC = gcc +AR = ar +CFLAGS = -std=c99 -O2 -Wall -Wextra +ARFLAGS = rcs +TARGET = libCgeScanline.a + +SOURCES = Scanline.c Bezier.c +OBJECTS = $(SOURCES:.c=.o) + +.PHONY: all clean + +all: $(TARGET) + +$(TARGET): $(OBJECTS) + $(AR) $(ARFLAGS) $@ $^ + +%.o: %.c CgeScanline.h + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + del $(OBJECTS) $(TARGET) 2>nul || exit 0 diff --git a/Makefile.posix b/Makefile.posix new file mode 100644 index 0000000..4c7d539 --- /dev/null +++ b/Makefile.posix @@ -0,0 +1,33 @@ +# POSIX Makefile for CgeScanline + +CC = gcc +AR = ar +CFLAGS = -std=c99 -O2 -Wall -Wextra -fPIC +ARFLAGS = rcs +TARGET = libCgeScanline.a + +SOURCES = Scanline.c Bezier.c +OBJECTS = $(SOURCES:.c=.o) + +.PHONY: all clean install + +all: $(TARGET) + +$(TARGET): $(OBJECTS) + $(AR) $(ARFLAGS) $@ $^ + +%.o: %.c CgeScanline.h + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + rm -f $(OBJECTS) $(TARGET) + +install: $(TARGET) + cp $(TARGET) /usr/local/lib/ + cp CgeScanline.h /usr/local/include/ + ldconfig || echo "Run ldconfig manually if needed" + +uninstall: + rm -f /usr/local/lib/libCgeScanline.a + rm -f /usr/local/include/CgeScanline.h + ldconfig || true diff --git a/Makefile.win32 b/Makefile.win32 new file mode 100644 index 0000000..157eaad --- /dev/null +++ b/Makefile.win32 @@ -0,0 +1,23 @@ +# Makefile.win32 for MSVC (NMake) +# Usage: Open "x86 Native Tools Command Prompt", then: +# nmake -f Makefile.win32 + +CC = cl +LIB = lib +CFLAGS = /c /nologo /W3 /O2 +LIBFLAGS = /nologo +TARGET = CgeScanline.lib + +SOURCES = Scanline.c Bezier.c +OBJECTS = $(SOURCES:.c=.obj) + +$(TARGET): $(OBJECTS) + $(LIB) $(LIBFLAGS) /OUT:$(TARGET) $(OBJECTS) + +{.}.c{}.obj: + $(CC) $(CFLAGS) /Fo$@ $< + +clean: + del $(OBJECTS) $(TARGET) 2>nul + +.PHONY: clean diff --git a/README.md b/README.md new file mode 100644 index 0000000..11fba4c --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# CgeScanline + +TODO + +## License + +0BSD - a permissive license with no attribution required. + diff --git a/Scanline.c b/Scanline.c new file mode 100644 index 0000000..dfb95a9 --- /dev/null +++ b/Scanline.c @@ -0,0 +1,296 @@ +#include "CgeScanline.h" +#include + +#define AET_INIT_SIZE 256 +#define SPAN_INIT_SIZE 256 + +#define SWAP(x, y) \ + do { uint32_t t = (x); (x) = (y); (y) = t; } while (0) + +#define SETC(x, value) \ + do { if (x) *(x) = (value); } while(0) + +typedef size_t (*SpanGeneratorCb)(CgeScanlineEdge *, CgeScanlineEdgeTable *, + size_t, CgeScanlineSpan *, size_t); + +static void aetStep(CgeScanlineEdge *edge, CgeScanlineEdgeTable *aet, + size_t size) { + CgeScanlineEdgeTable *current, *end; + + for (current = aet, end = aet + size; current != end; ++current) { + CgeScanlineEdge *lookupEdge = edge + current->index; + uint32_t overflow; + + current->x += lookupEdge->step; + current->error += lookupEdge->error; + + overflow = (current->error >= lookupEdge->threshold) ? 0xFFFFFFFFul : 0; + current->x += lookupEdge->dir & overflow; + current->error -= lookupEdge->threshold & overflow; + } +} + +static void aetSort(CgeScanlineEdgeTable *aet, size_t aetSize) { + CgeScanlineEdgeTable tmp; + size_t i, j; + + for (i = 1; i < aetSize; ++i) { + tmp = aet[i]; + for (j = i; j > 0 && aet[j - 1].x > tmp.x; --j) + aet[j] = aet[j - 1]; + aet[j] = tmp; + } +} + +static size_t aetRemove(CgeScanlineEdge *edge, CgeScanlineEdgeTable *aet, + size_t aetSize, uint32_t y) { + size_t writeIdx, i; + + for (writeIdx = 0, i = 0; i < aetSize; ++i) { + if (edge[aet[i].index].endY != y) + aet[writeIdx++] = aet[i]; + } + + return writeIdx; +} + +static int aetAdd(CgeScanlineEdge *edge, size_t edgeSize, + CgeScanlineEdgeTable *aet, size_t *aetSize, + size_t aetCapacity, size_t *edgeCursor, uint32_t y) { + for (; *edgeCursor < edgeSize && edge[*edgeCursor].startY == y; ++*edgeCursor) { + if (*aetSize >= aetCapacity) + return 0; + aet[*aetSize].index = *edgeCursor; + aet[*aetSize].x = edge[*edgeCursor].startX; + aet[*aetSize].error = 0; + ++*aetSize; + } + return 1; +} + +static int edgesCmp(const void *lhs, const void *rhs) { + CgeScanlineEdge *left = (CgeScanlineEdge *)lhs; + CgeScanlineEdge *right = (CgeScanlineEdge *)rhs; + + return left->startY < right->startY ? -1 : (left->startY == right->startY ? 0 : 1); +} + +int CgeScanlineEdgeAdd(CgeScanlineEdge *edge, uint32_t aX, uint32_t aY, + uint32_t bX, uint32_t bY) { + uint32_t dX; + + if (aY == bY) + return 0; + + edge->winding = 1; + if (bY < aY) { + SWAP(aX, bX); + SWAP(aY, bY); + edge->winding = -1; + } + + edge->threshold = bY - aY; + edge->startY = aY; + edge->endY = bY; + edge->startX = aX; + dX = (aX < bX) ? bX - aX : aX - bX; + edge->step = dX / edge->threshold; + edge->error = dX % edge->threshold; + edge->dir = 1; + + if (bX < aX) { + edge->dir = -1; + edge->step *= -1; + } + + return 1; +} + +static size_t spansOddEven(CgeScanlineEdge *edge, CgeScanlineEdgeTable *aet, + size_t aetSize, CgeScanlineSpan *span, + size_t spanSize) { + size_t i, size; + (void)edge; + + for (i = 0, size = 0; i + 1 < aetSize; i += 2) { + if (aet[i].x < aet[i + 1].x) { + if (size >= spanSize) + return SIZE_MAX; + span[size].start = aet[i].x; + span[size].end = aet[i + 1].x; + ++size; + } + } + + return size; +} + +static size_t spansNonZero(CgeScanlineEdge *edge, CgeScanlineEdgeTable *aet, + size_t aetSize, CgeScanlineSpan *span, + size_t spanSize) { + int winding, inSpan; + uint32_t spanStart; + size_t i, size; + + winding = 0; + inSpan = 0; + spanStart = 0; + size = 0; + + for (i = 0; i < aetSize; ++i) { + winding += edge[aet[i].index].winding; + + if (!inSpan && winding != 0) { + spanStart = aet[i].x; + inSpan = 1; + } else if (inSpan && winding == 0) { + if (spanStart < aet[i].x) { + if (size >= spanSize) + return SIZE_MAX; + span[size].start = spanStart; + span[size].end = aet[i].x; + ++size; + } + inSpan = 0; + } + } + + return size; +} + +int CgeScanlineStateInit(CgeScanlineState *state, CgeScanlineEdge *edge, + size_t edgeSize, CgeScanlineEdgeTable *aet, + size_t aetCapacity, CgeScanlineSpan *span, + size_t spanCapacity, CgeScanlineRowCallback callback, + void *userData, int flags) { + uint32_t yMin, yMax; + size_t i; + + if (!edgeSize || !aetCapacity || !spanCapacity || !callback) + return 0; + + state->edge = edge; + state->edgeSize = edgeSize; + state->aet = aet; + state->aetCapacity = aetCapacity; + state->span = span; + state->spanCapacity = spanCapacity; + state->callback = callback; + state->userData = userData; + state->flags = flags; + + yMin = state->edge[0].startY; + yMax = state->edge[0].endY; + for (i = 1; i < edgeSize; ++i) { + if (state->edge[i].startY < yMin) + yMin = state->edge[i].startY; + if (state->edge[i].endY > yMax) + yMax = state->edge[i].endY; + } + + qsort(state->edge, state->edgeSize, sizeof(*state->edge), edgesCmp); + state->yCurrent = yMin; + state->yMax = yMax; + state->edgeCursor = 0; + state->aetSize = 0; + + return 1; +} + +int CgeScanlineStateNext(CgeScanlineState *state, int *result) { + SpanGeneratorCb generate; + size_t rowSpans; + + generate = (state->flags & CGE_SCANLINE_NON_ZERO) ? spansNonZero + : spansOddEven; + + if (state->yCurrent >= state->yMax) { + SETC(result, CGE_SCANLINE_EDONE); + return 0; + } + + state->aetSize = aetRemove(state->edge, state->aet, state->aetSize, + state->yCurrent); + if (!aetAdd(state->edge, state->edgeSize, state->aet, &state->aetSize, + state->aetCapacity, &state->edgeCursor, state->yCurrent)) { + SETC(result, CGE_SCANLINE_EAET); + return 0; + } + + rowSpans = 0; + if (state->aetSize != 0) { + aetSort(state->aet, state->aetSize); + + rowSpans = generate(state->edge, state->aet, state->aetSize, + state->span, state->spanCapacity); + if (rowSpans == SIZE_MAX) { + SETC(result, CGE_SCANLINE_ESPANS); + return 0; + } + aetStep(state->edge, state->aet, state->aetSize); + } + + state->callback(state->yCurrent, state->span, rowSpans, state->userData); + state->yCurrent++; + return 1; +} + +int CgeScanlineSpans(CgeScanlineEdge *edge, size_t edgeSize, + CgeScanlineRowCallback callback, void *userData, + int flags) { + CgeScanlineEdgeTable *aet; + CgeScanlineSpan *spans; + CgeScanlineState state; + int result; + + aet = malloc(sizeof(*aet) * AET_INIT_SIZE); + spans = malloc(sizeof(*spans) * SPAN_INIT_SIZE); + if (!aet || !spans) { + free(aet); + free(spans); + return 0; + } + + if (!CgeScanlineStateInit(&state, edge, edgeSize, aet, AET_INIT_SIZE, spans, + SPAN_INIT_SIZE, callback, userData, flags)) { + free(aet); + free(spans); + return 0; + } + + while (1) { + if (CgeScanlineStateNext(&state, &result)) + continue; + + switch (result) { + case CGE_SCANLINE_EDONE: + free(state.aet); + free(state.span); + return 1; + + case CGE_SCANLINE_EAET: + aet = realloc(state.aet, sizeof(*aet) * state.aetCapacity * 2); + if (aet) { + state.aetCapacity *= 2; + state.aet = aet; + } else { + free(state.aet); + free(state.span); + return 0; + } + break; + + case CGE_SCANLINE_ESPANS: + spans = realloc(state.span, sizeof(*spans) * state.spanCapacity * 2); + if (spans) { + state.spanCapacity *= 2; + state.span = spans; + } else { + free(state.aet); + free(state.span); + return 0; + } + break; + } + } +}