@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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 */
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,8 @@
|
||||
# CgeScanline
|
||||
|
||||
TODO
|
||||
|
||||
## License
|
||||
|
||||
0BSD - a permissive license with no attribution required.
|
||||
|
||||
+296
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user