@@ -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