Initial commit

This commit is contained in:
2026-09-15 14:37:22 +03:00
commit d7b3db31b2
13 changed files with 1071 additions and 0 deletions
+89
View File
@@ -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
+68
View File
@@ -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
+167
View File
@@ -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;
}
+26
View File
@@ -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)
+111
View File
@@ -0,0 +1,111 @@
#ifndef CGE_SCANLINE_H
#define CGE_SCANLINE_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stddef.h>
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 */
+12
View File
@@ -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.
+29
View File
@@ -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
+23
View File
@@ -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
+33
View File
@@ -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
+23
View File
@@ -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
+7
View File
@@ -0,0 +1,7 @@
# CgeScanline
TODO
## License
0BSD - a permissive license with no attribution required.
+296
View File
@@ -0,0 +1,296 @@
#include "CgeScanline.h"
#include <stdlib.h>
#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;
}
}
}
+187
View File
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}