commit d7b3db31b2e899a9d8d34502739db2c0fb216a94 Author: Mikhail Romanko Date: Sun Sep 13 22:31:21 2026 +0300 Initial commit 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..37e529f --- /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 */ \ No newline at end of file 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..42bebd6 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# 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; + } + } +} diff --git a/demo_bezier.c b/demo_bezier.c new file mode 100644 index 0000000..f2daf1e --- /dev/null +++ b/demo_bezier.c @@ -0,0 +1,187 @@ +/* + * demo_bezier.c + * + * Demonstrates the Bezier curve flattening helpers in CgeScanline. + * Uses 8x5 sub-pixel coverage anti-aliasing (8 sub-pixels in X, + * 5 sub-scanlines in Y per pixel). + * + * Coordinate transform: + * x' = x * 8 + 4 + * y' = y * 5 + 2 + * (centered in the pixel for correct coverage) + * + * Coverage per scanline is accumulated via a LUT that maps 0..8 + * sub-pixel X-coverage to 0..51. With 5 scanlines, a fully covered + * pixel reaches 5 * 51 = 255. + * + * Renders "teardrop" shape to a PPM image: + * + * Compile with: + * gcc -std=c99 -Wall -Wextra -o demo_bezier.exe demo_bezier.c Scanline.c + * + * Run: + * demo_bezier.exe + * View output_bezier.ppm (open in any image viewer / convert to PNG). + */ + +#include "CgeScanline.h" +#include +#include +#include + +#define WIDTH 200 +#define HEIGHT 200 + +/* 8x5 subsampling factors */ +#define SUB_X 8 +#define SUB_Y 5 +#define SUB_X_SHIFT 3 +#define SUB_Y_SHIFT 0 +#define SUB_X_OFFSET (SUB_X / 2) +#define SUB_Y_OFFSET (SUB_Y / 2) +#define SUB_X_MASK 7 + +static const uint8_t covLut[9] = { + 0, 6, 13, 19, 26, 32, 38, 45, 51, +}; + +static uint8_t covBuf[HEIGHT][WIDTH]; + +static void toSubPixel(int *sx, int *sy, int ix, int iy) { + *sx = ix * SUB_X + SUB_X_OFFSET; + *sy = iy * SUB_Y + SUB_Y_OFFSET; +} + +static void fillRow(uint32_t y, CgeScanlineSpan *spans, size_t size, + void *userData) { + (void)userData; + + uint32_t row = y / SUB_Y; + if (row >= HEIGHT) + return; + + for (size_t i = 0; i < size; ++i) { + uint32_t s = spans[i].start; + uint32_t e = spans[i].end; + + if (s >= e) continue; + + uint32_t colStart = s >> SUB_X_SHIFT; + uint32_t colEnd = (e - 1) >> SUB_X_SHIFT; + + if (colStart >= WIDTH) + continue; + if (colEnd >= WIDTH) + colEnd = WIDTH - 1; + if (colStart > colEnd) + continue; + + /* Span covers only one pixel (partially or entirely) */ + if (colStart == colEnd) { + uint32_t add = covLut[e - s]; + uint32_t cur = (uint32_t)covBuf[row][colStart] + add; + covBuf[row][colStart] = cur > 255 ? 255 : (uint8_t)cur; + continue; + } + + { + uint32_t n = SUB_X - (s & SUB_X_MASK); + uint32_t cur = (uint32_t)covBuf[row][colStart] + covLut[n]; + covBuf[row][colStart] = cur > 255 ? 255 : (uint8_t)cur; + } + + for (uint32_t col = colStart + 1; col < colEnd; ++col) { + uint32_t cur = (uint32_t)covBuf[row][col] + covLut[SUB_X]; + covBuf[row][col] = cur > 255 ? 255 : (uint8_t)cur; + } + + { + uint32_t n = e - (colEnd << SUB_X_SHIFT); + uint32_t cur = (uint32_t)covBuf[row][colEnd] + covLut[n]; + covBuf[row][colEnd] = cur > 255 ? 255 : (uint8_t)cur; + } + } +} + +static void writePPM(const char *filename) { + FILE *f = fopen(filename, "wb"); + int y, x; + + if (!f) { + perror(filename); + return; + } + + fprintf(f, "P6\n%d %d\n255\n", WIDTH, HEIGHT); + for (y = 0; y < HEIGHT; ++y) { + for (x = 0; x < WIDTH; ++x) { + unsigned char pixel = (unsigned char)(255 - covBuf[y][x]); + fwrite(&pixel, 1, 1, f); + fwrite(&pixel, 1, 1, f); + fwrite(&pixel, 1, 1, f); + } + } + + fclose(f); + printf("Wrote %s (%dx%d)\n", filename, WIDTH, HEIGHT); +} + +static size_t buildBezierCubicEdges(CgeScanlineEdge *edge, size_t edgeSize, + int iX0, int iY0, + int iX1, int iY1, + int iX2, int iY2, + int iX3, int iY3, + uint32_t flatness) { + int sx0, sy0, sx1, sy1, sx2, sy2, sx3, sy3; + + toSubPixel(&sx0, &sy0, iX0, iY0); + toSubPixel(&sx1, &sy1, iX1, iY1); + toSubPixel(&sx2, &sy2, iX2, iY2); + toSubPixel(&sx3, &sy3, iX3, iY3); + return CgeScanlineBezierCubicEdges(edge, edgeSize, + (uint32_t)sx0, (uint32_t)sy0, + (uint32_t)sx1, (uint32_t)sy1, + (uint32_t)sx2, (uint32_t)sy2, + (uint32_t)sx3, (uint32_t)sy3, + flatness); +} + +int main(void) { + CgeScanlineEdge edgeBuf[16384]; + size_t edgeCount; + + memset(covBuf, 0, sizeof(covBuf)); + printf("=== Demo: Bezier Flattening (8x5 subsampled) ===\n\n"); + printf("Rounded shape (two cubic Beziers, flatness=1)\n"); + + edgeCount = 0; + + edgeCount = buildBezierCubicEdges( + edgeBuf, 16384, + 100, 20, /* P0: top centre */ + 90, 20, /* P1: left top */ + 10, 180, /* P2: left bottom */ + 100, 180, /* P3: bottom centre */ + 2); + printf(" Left cubic: %zu edges\n", edgeCount); + + edgeCount += buildBezierCubicEdges( + edgeBuf + edgeCount, 16384 - edgeCount, + 100, 180, /* P0: bottom centre */ + 190, 180, /* P1: right bottom */ + 110, 20, /* P2: right top */ + 100, 20, /* P3: top centre */ + 2); + printf(" Right cubic: %zu edges (cumulative)\n", edgeCount); + printf(" Total: %zu edges\n", edgeCount); + + { + int ok = CgeScanlineSpans(edgeBuf, edgeCount, fillRow, NULL, + CGE_SCANLINE_ODD_EVEN); + printf(" Raster: %s\n\n", ok ? "OK" : "FAILED"); + } + + writePPM("output_bezier.ppm"); + printf("Done.\n"); + return 0; +}