mirror of https://gitlab.com/nakst/essence
957 lines
30 KiB
C++
957 lines
30 KiB
C++
// This file is part of the Essence operating system.
|
|
// It is released under the terms of the MIT license -- see LICENSE.md.
|
|
// Written by: nakst.
|
|
|
|
// TODO Fix glitches.
|
|
// TODO RAST_REPEAT_NORMAL is wrong with negative values.
|
|
|
|
#if defined(IN_DESIGNER) && !defined(DESIGNER2)
|
|
#define RAST_ARRAY(x) x *
|
|
#define RAST_ARRAY_ADD arrput
|
|
#define RAST_ARRAY_CLEAR arrclear
|
|
#define RAST_ARRAY_DELETE_SWAP arrdelswap
|
|
#define RAST_ARRAY_FREE arrfree
|
|
#define RAST_ARRAY_INSERT arrinsn
|
|
#define RAST_ARRAY_LAST arrlast
|
|
#define RAST_ARRAY_LENGTH arrlen
|
|
#define RAST_ARRAY_LENGTH_U arrlenu
|
|
#else
|
|
#define RAST_ARRAY(x) Array<x>
|
|
#define RAST_ARRAY_ADD(x, y) ((x).Add(y))
|
|
#define RAST_ARRAY_CLEAR(x) ((x).SetLength(0))
|
|
#define RAST_ARRAY_DELETE_SWAP(x, y) ((x).DeleteSwap(y))
|
|
#define RAST_ARRAY_FREE(x) ((x).Free())
|
|
#define RAST_ARRAY_INSERT(x, y, z) ((x).InsertMany(y, z))
|
|
#define RAST_ARRAY_LAST(x) ((x).Last())
|
|
#define RAST_ARRAY_LENGTH(x) ((intptr_t) (x).Length())
|
|
#define RAST_ARRAY_LENGTH_U(x) ((x).Length())
|
|
#endif
|
|
|
|
typedef struct RastVertex {
|
|
float x, y;
|
|
} RastVertex;
|
|
|
|
typedef struct RastEdge {
|
|
float xf, yf, xt, yt;
|
|
float dx, dy;
|
|
int sign;
|
|
} RastEdge;
|
|
|
|
typedef struct RastShape {
|
|
RAST_ARRAY(RastEdge) edges; /* sorted by yf; yf <= yt */
|
|
int left, right, top, bottom;
|
|
} RastShape;
|
|
|
|
typedef struct RastSurface {
|
|
uint32_t *buffer;
|
|
int width, height, stride;
|
|
float *area, *areaFill;
|
|
bool customBuffer;
|
|
} RastSurface;
|
|
|
|
typedef enum RastPaintType {
|
|
RAST_PAINT_NONE,
|
|
RAST_PAINT_SOLID,
|
|
RAST_PAINT_CHECKERBOARD,
|
|
RAST_PAINT_LINEAR_GRADIENT,
|
|
RAST_PAINT_RADIAL_GRADIENT,
|
|
RAST_PAINT_ANGULAR_GRADIENT,
|
|
RAST_PAINT_NOISE,
|
|
} RastPaintType;
|
|
|
|
typedef enum RastRepeatMode {
|
|
RAST_REPEAT_CLAMP,
|
|
RAST_REPEAT_NORMAL,
|
|
RAST_REPEAT_MIRROR,
|
|
} RastRepeatMode;
|
|
|
|
typedef struct RastPaint {
|
|
RastPaintType type;
|
|
|
|
union {
|
|
struct {
|
|
uint32_t color;
|
|
float alpha;
|
|
} solid;
|
|
|
|
struct {
|
|
uint32_t color1, color2;
|
|
float alpha1, alpha2;
|
|
int size;
|
|
} checkboard;
|
|
|
|
struct {
|
|
uint32_t *color;
|
|
float *alpha;
|
|
float transform[6];
|
|
RastRepeatMode repeatMode;
|
|
} gradient;
|
|
|
|
struct {
|
|
uint32_t color;
|
|
float minimum, maximum;
|
|
} noise;
|
|
};
|
|
} RastPaint;
|
|
|
|
typedef struct RastGradientStop {
|
|
uint32_t color;
|
|
float position;
|
|
} RastGradientStop;
|
|
|
|
typedef struct RastPathSegment {
|
|
uintptr_t uptoVertex;
|
|
} RastPathSegment;
|
|
|
|
typedef struct RastPath {
|
|
RAST_ARRAY(RastPathSegment) segments;
|
|
RAST_ARRAY(RastVertex) vertices;
|
|
} RastPath;
|
|
|
|
typedef enum RastLineJoinMode {
|
|
// Determines how convex segments are joined.
|
|
// Concave segments are always mitered with infinite limit.
|
|
|
|
RAST_LINE_JOIN_MITER, // Force bevels with miterLimit = 0.
|
|
RAST_LINE_JOIN_ROUND,
|
|
} RastLineJoinMode;
|
|
|
|
typedef enum RastLineCapMode {
|
|
RAST_LINE_CAP_FLAT,
|
|
RAST_LINE_CAP_SQUARE,
|
|
RAST_LINE_CAP_ROUND,
|
|
} RastLineCapMode;
|
|
|
|
typedef struct RastContourStyle {
|
|
float internalWidth, externalWidth;
|
|
RastLineJoinMode joinMode;
|
|
float miterLimit;
|
|
RastLineCapMode capMode;
|
|
// TODO Markers.
|
|
} RastContourStyle;
|
|
|
|
typedef struct RastDash {
|
|
float length, gap;
|
|
RastContourStyle *style;
|
|
} RastDash;
|
|
|
|
#define RAST_ADD_VERTEX_MINIMUM_DISTANCE_SQUARED (0.1f * 0.1f)
|
|
#define RAST_GRADIENT_COLORS (256)
|
|
#define RAST_ROUND_TOLERANCE (0.25f)
|
|
#define RAST_FLATTEN_TOLERANCE (0.25f)
|
|
#define RAST_GRADIENT_NOISE (0.005f)
|
|
|
|
#define RAST_AVERAGE_VERTICES(a, b) { ((a).x + (b).x) * 0.5f, ((a).y + (b).y) * 0.5f }
|
|
|
|
bool RastSurfaceInitialise(RastSurface *surface, int width, int height, bool customBuffer) {
|
|
surface->width = width;
|
|
surface->height = height;
|
|
surface->stride = 4 * width;
|
|
surface->area = (float *) EsHeapAllocate(sizeof(float) * width, true);
|
|
surface->areaFill = (float *) EsHeapAllocate(sizeof(float) * (width + 1), true);
|
|
surface->customBuffer = customBuffer;
|
|
|
|
if (!customBuffer) {
|
|
surface->buffer = (uint32_t *) EsHeapAllocate(4 * width * height, true);
|
|
}
|
|
|
|
return surface->buffer && surface->area && surface->areaFill;
|
|
}
|
|
|
|
void RastSurfaceDestroy(RastSurface *surface) {
|
|
EsHeapFree(surface->area);
|
|
EsHeapFree(surface->areaFill);
|
|
|
|
if (!surface->customBuffer) {
|
|
EsHeapFree(surface->buffer);
|
|
}
|
|
}
|
|
|
|
#ifndef IN_DESIGNER
|
|
ES_MACRO_SORT(RastEdgesSort, RastEdge, { result = _left->yf > _right->yf ? 1 : _left->yf < _right->yf ? -1 : 0; }, void *);
|
|
#else
|
|
int _RastEdgeCompare(const void *left, const void *right) {
|
|
const RastEdge *_left = (const RastEdge *) left;
|
|
const RastEdge *_right = (const RastEdge *) right;
|
|
return _left->yf > _right->yf ? 1 : _left->yf < _right->yf ? -1 : 0;
|
|
}
|
|
|
|
void RastEdgesSort(RastEdge *edges, size_t count, void *_unused) {
|
|
(void) _unused;
|
|
qsort(edges, count, sizeof(RastEdge), _RastEdgeCompare);
|
|
}
|
|
#endif
|
|
|
|
float _RastRepeat(RastRepeatMode mode, float p) {
|
|
if (mode == RAST_REPEAT_CLAMP) {
|
|
if (p < 0) return 0;
|
|
if (p > 1) return 1;
|
|
return p;
|
|
} else if (mode == RAST_REPEAT_MIRROR) {
|
|
p = EsCRTfabsf(EsCRTfmodf(p, 2.0f));
|
|
if (p > 1) return 2 - p;
|
|
return p;
|
|
} else {
|
|
return EsCRTfabsf(EsCRTfmodf(p, 1.0f));
|
|
}
|
|
}
|
|
|
|
void _RastShapeDestroy(RastShape shape) {
|
|
RAST_ARRAY_FREE(shape.edges);
|
|
}
|
|
|
|
void RastSurfaceFill(RastSurface surface, RastShape shape, RastPaint paint, bool evenOdd) {
|
|
if (paint.type == RAST_PAINT_LINEAR_GRADIENT
|
|
|| paint.type == RAST_PAINT_RADIAL_GRADIENT
|
|
|| paint.type == RAST_PAINT_ANGULAR_GRADIENT) {
|
|
if (!paint.gradient.color || !paint.gradient.alpha) {
|
|
_RastShapeDestroy(shape);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (RAST_ARRAY_LENGTH(shape.edges) == 0) {
|
|
return;
|
|
}
|
|
|
|
RAST_ARRAY(RastEdge) active = { 0 };
|
|
int edgePosition = 0;
|
|
|
|
if (shape.left < 0) shape.left = 0;
|
|
if (shape.right > surface.width) shape.right = surface.width;
|
|
if (shape.top < 0) shape.top = 0;
|
|
if (shape.bottom > surface.height) shape.bottom = surface.height;
|
|
|
|
// Split edges that cross the left side of the shape.
|
|
|
|
int initialShapeEdges = RAST_ARRAY_LENGTH(shape.edges);
|
|
|
|
for (int i = 0; i < initialShapeEdges; i++) {
|
|
RastEdge *a = &shape.edges[i];
|
|
|
|
float y0 = a->yf;
|
|
float y1 = a->yt;
|
|
float x0 = a->xf;
|
|
float x1 = a->xt;
|
|
bool flipped = false;
|
|
|
|
if (x0 > x1) {
|
|
float t = x0;
|
|
x0 = x1, x1 = t;
|
|
flipped = true;
|
|
}
|
|
|
|
if (x0 < shape.left && x1 >= shape.left) {
|
|
RastEdge e = *a;
|
|
|
|
if (flipped) {
|
|
y1 += (shape.left - x0) * a->dy;
|
|
a->xt = e.xf = e.xt = shape.left;
|
|
e.yf = a->yt = y1;
|
|
e.dx = 0;
|
|
} else {
|
|
y0 += (shape.left - x0) * a->dy;
|
|
a->xt = e.xf = a->xf = shape.left;
|
|
e.yf = a->yt = y0;
|
|
a->dx = 0;
|
|
}
|
|
|
|
RAST_ARRAY_ADD(shape.edges, e);
|
|
}
|
|
}
|
|
|
|
RastEdgesSort(&shape.edges[0], RAST_ARRAY_LENGTH(shape.edges), NULL);
|
|
|
|
if (paint.type == RAST_PAINT_CHECKERBOARD && paint.checkboard.size < 1) paint.checkboard.size = 1;
|
|
|
|
for (int scanline = shape.top; scanline < shape.bottom; scanline++) {
|
|
// Remove edges above this scanline.
|
|
|
|
for (int i = 0; i < RAST_ARRAY_LENGTH(active); i++) {
|
|
if (active[i].yt < scanline) {
|
|
RAST_ARRAY_DELETE_SWAP(active, i);
|
|
i--;
|
|
}
|
|
}
|
|
|
|
// Add edges that start within this scanline.
|
|
|
|
while (edgePosition < RAST_ARRAY_LENGTH(shape.edges)) {
|
|
RastEdge *e = &shape.edges[edgePosition];
|
|
|
|
if (e->yf < scanline + 1.0f) {
|
|
RAST_ARRAY_ADD(active, *e);
|
|
edgePosition++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If there are no active edges, don't process the scanline.
|
|
|
|
if (!RAST_ARRAY_LENGTH(active)) {
|
|
continue;
|
|
}
|
|
|
|
// Calculate the signed area covered by each active edge.
|
|
|
|
for (int i = 0; i < RAST_ARRAY_LENGTH(active); i++) {
|
|
RastEdge *a = &active[i];
|
|
|
|
// Calculate the range of pixels the edge crosses on the scanline.
|
|
|
|
float top = scanline, bottom = scanline + 1;
|
|
float y0 = (a->yf > top) ? a->yf : top;
|
|
float y1 = (a->yt < bottom) ? a->yt : bottom;
|
|
float x0 = (y0 - a->yf) * a->dx + a->xf;
|
|
float x1 = (y1 - a->yf) * a->dx + a->xf;
|
|
float dy = a->dy;
|
|
bool flipped = false;
|
|
|
|
if (x1 < x0) {
|
|
// Convert NE-SW edge to NW-SE.
|
|
// Flipping the edge preserves signed area.
|
|
|
|
float t = y0;
|
|
y0 = top + bottom - y1;
|
|
y1 = top + bottom - t;
|
|
t = x0, x0 = x1, x1 = t;
|
|
dy = -dy;
|
|
flipped = true;
|
|
}
|
|
|
|
if (x1 < shape.left) {
|
|
x0 = x1 = shape.left;
|
|
} else if (x0 < shape.left) {
|
|
y0 += (shape.left - x0) * dy;
|
|
x0 = shape.left;
|
|
}
|
|
|
|
if (x1 >= shape.right) {
|
|
y1 += (x1 - shape.right + 1) * dy;
|
|
x1 = shape.right - 1;
|
|
}
|
|
|
|
if (y1 <= y0 || x0 >= shape.right || x1 < shape.left || EsCRTisnanf(x0) || EsCRTisnanf(x1) || EsCRTisnanf(y0) || EsCRTisnanf(y1)) {
|
|
continue;
|
|
}
|
|
|
|
if (EsCRTfloorf(x0) == EsCRTfloorf(x1)) {
|
|
// Edge crosses one pixel on this scanline,
|
|
// forming a trapezium.
|
|
|
|
float right = EsCRTfloorf(x0 + 1);
|
|
float p = a->sign * (y1 - y0);
|
|
int xs = (int) x0;
|
|
surface.area[xs] += p * (right - x0 + right - x1) * 0.5f;
|
|
surface.areaFill[xs + 1] += p;
|
|
} else {
|
|
// Edge crosses multiple pixels on this scanline.
|
|
// The first pixel is a triangle.
|
|
|
|
float tx = EsCRTfloorf(x0 + 1);
|
|
float th = dy * (tx - x0);
|
|
int xs = (int) x0, xf = (int) x1;
|
|
surface.area[xs] += a->sign * (tx - x0) * th * 0.5f;
|
|
|
|
// The middle pixels are trapeziums.
|
|
|
|
float pa = th + 0.5f * dy;
|
|
for (int j = xs + 1; j < xf; j++, pa += dy) surface.area[j] += a->sign * pa;
|
|
|
|
// The final pixel is a removed triangle.
|
|
|
|
float p = a->sign * (y1 - y0);
|
|
float fx = EsCRTfloorf(x1);
|
|
float fy = a->yf + a->dy * (fx - a->xf);
|
|
if (flipped) fy = top + bottom - fy;
|
|
surface.area[xf] += p - a->sign * 0.5f * (x1 - fx) * (y1 - fy);
|
|
surface.areaFill[xf + 1] += p;
|
|
}
|
|
}
|
|
|
|
// Calculate the final coverage of each pixel.
|
|
|
|
float cumulativeArea = 0;
|
|
uint32_t *destination = (uint32_t *) ((uint8_t *) surface.buffer + scanline * surface.stride + 4 * shape.left);
|
|
|
|
float textureDX = paint.gradient.transform[0];
|
|
float texturePX = shape.left * textureDX + (float) scanline * paint.gradient.transform[1] + paint.gradient.transform[2];
|
|
float textureDY = paint.gradient.transform[3];
|
|
float texturePY = shape.left * textureDY + (float) scanline * paint.gradient.transform[4] + paint.gradient.transform[5];
|
|
|
|
if (paint.type == RAST_PAINT_NOISE) {
|
|
textureDX = 1;
|
|
texturePX = 0;
|
|
}
|
|
|
|
for (int i = shape.left; i < shape.right; i++) {
|
|
cumulativeArea += surface.areaFill[i];
|
|
float a = surface.area[i] + cumulativeArea;
|
|
|
|
if (evenOdd) {
|
|
a = EsCRTfmodf(AbsoluteFloat(a), 2);
|
|
if (a > 1) a = 2 - a;
|
|
} else {
|
|
if (a < 0) a = -a;
|
|
if (a > 1) a = 1;
|
|
}
|
|
|
|
if (a > 0.0039f) {
|
|
if (paint.type == RAST_PAINT_SOLID) {
|
|
uint8_t c = (uint8_t) (a * paint.solid.alpha * 255.0f);
|
|
if (c) BlendPixel(destination, (c << 24) | paint.solid.color, true);
|
|
} else if (paint.type == RAST_PAINT_CHECKERBOARD) {
|
|
if (((i - shape.left) / paint.checkboard.size + (scanline - shape.top) / paint.checkboard.size) & 1) {
|
|
uint8_t c = (uint8_t) (a * paint.checkboard.alpha2 * 255.0f);
|
|
if (c) BlendPixel(destination, (c << 24) | paint.checkboard.color2, true);
|
|
} else {
|
|
uint8_t c = (uint8_t) (a * paint.checkboard.alpha1 * 255.0f);
|
|
if (c) BlendPixel(destination, (c << 24) | paint.checkboard.color1, true);
|
|
}
|
|
} else if (paint.type == RAST_PAINT_LINEAR_GRADIENT) {
|
|
float p = _RastRepeat(paint.gradient.repeatMode, texturePX);
|
|
int pi = (int) ((RAST_GRADIENT_COLORS - 1) * p);
|
|
EsAssert(pi >= 0 && pi < RAST_GRADIENT_COLORS); // Invalid gradient index.
|
|
uint8_t c = (uint8_t) (a * paint.gradient.alpha[pi] * 255.0f);
|
|
if (c) BlendPixel(destination, (c << 24) | paint.gradient.color[pi], true);
|
|
} else if (paint.type == RAST_PAINT_RADIAL_GRADIENT) {
|
|
float p = _RastRepeat(paint.gradient.repeatMode, EsCRTsqrtf(texturePX * texturePX + texturePY * texturePY));
|
|
int pi = (int) ((RAST_GRADIENT_COLORS - 1) * p);
|
|
uint8_t c = (uint8_t) (a * paint.gradient.alpha[pi] * 255.0f);
|
|
if (c) BlendPixel(destination, (c << 24) | paint.gradient.color[pi], true);
|
|
} else if (paint.type == RAST_PAINT_ANGULAR_GRADIENT) {
|
|
float p = _RastRepeat(paint.gradient.repeatMode, EsCRTatan2f(texturePY, texturePX) * 0.159154943091f + 0.5f);
|
|
int pi = (int) ((RAST_GRADIENT_COLORS - 1) * p);
|
|
uint8_t c = (uint8_t) (a * paint.gradient.alpha[pi] * 255.0f);
|
|
if (c) BlendPixel(destination, (c << 24) | paint.gradient.color[pi], true);
|
|
} else if (paint.type == RAST_PAINT_NOISE) {
|
|
union { float f; uint32_t u; } noise = { texturePX + texturePY };
|
|
noise.u += noise.u << 10;
|
|
noise.u ^= noise.u >> 6;
|
|
noise.u += noise.u << 3;
|
|
noise.u ^= noise.u >> 11;
|
|
noise.u += noise.u << 15;
|
|
noise.u &= 0x7FFFFF;
|
|
noise.u |= 0x3F800000;
|
|
noise.f /= 2;
|
|
noise.f *= paint.noise.maximum - paint.noise.minimum;
|
|
noise.f += paint.noise.minimum;
|
|
|
|
uint8_t c = (uint8_t) (a * noise.f * 255.0f);
|
|
if (c) BlendPixel(destination, (c << 24) | paint.noise.color, true);
|
|
}
|
|
}
|
|
|
|
surface.area[i] = surface.areaFill[i] = 0;
|
|
destination++;
|
|
texturePX += textureDX;
|
|
texturePY += textureDY;
|
|
}
|
|
|
|
surface.areaFill[shape.right] = 0;
|
|
}
|
|
|
|
RAST_ARRAY_FREE(active);
|
|
_RastShapeDestroy(shape);
|
|
}
|
|
|
|
void _RastShapeAddEdges(RastShape *shape, RastVertex *vertices, int sign, bool open, int vertexCount) {
|
|
if (vertexCount <= 1) return;
|
|
|
|
for (int i = 0; i < vertexCount - (open ? 1 : 0); i++) {
|
|
RastVertex *from = &vertices[i];
|
|
RastVertex *to = &vertices[(i + 1) % vertexCount];
|
|
if (from->y == to->y) continue;
|
|
|
|
int p = RAST_ARRAY_LENGTH(shape->edges);
|
|
RAST_ARRAY_INSERT(shape->edges, p, 1);
|
|
RastEdge *edge = &shape->edges[p];
|
|
|
|
if (from->y < to->y) {
|
|
edge->xf = from->x, edge->yf = from->y;
|
|
edge->xt = to->x, edge->yt = to->y;
|
|
edge->sign = sign;
|
|
} else {
|
|
edge->xf = to->x, edge->yf = to->y;
|
|
edge->xt = from->x, edge->yt = from->y;
|
|
edge->sign = -sign;
|
|
}
|
|
|
|
edge->dx = (edge->xt - edge->xf) / (edge->yt - edge->yf);
|
|
edge->dy = 1.0f / edge->dx;
|
|
|
|
if (edge->xf < shape->left) shape->left = edge->xf;
|
|
if (edge->xt < shape->left) shape->left = edge->xt;
|
|
if (edge->yf < shape->top) shape->top = edge->yf;
|
|
if (edge->xf + 1 > shape->right) shape->right = edge->xf + 1;
|
|
if (edge->xt + 1 > shape->right) shape->right = edge->xt + 1;
|
|
if (edge->yt + 1 > shape->bottom) shape->bottom = edge->yt + 1;
|
|
}
|
|
}
|
|
|
|
void RastPathCloseSegment(RastPath *path) {
|
|
if (!RAST_ARRAY_LENGTH_U(path->segments) || RAST_ARRAY_LAST(path->segments).uptoVertex != RAST_ARRAY_LENGTH_U(path->vertices)) {
|
|
RastPathSegment segment = {};
|
|
segment.uptoVertex = RAST_ARRAY_LENGTH_U(path->vertices);
|
|
RAST_ARRAY_ADD(path->segments, segment);
|
|
}
|
|
}
|
|
|
|
RastShape RastShapeCreateSolid(RastPath *path) {
|
|
if (RAST_ARRAY_LENGTH(path->vertices) < 3) return (RastShape) { 0 };
|
|
RastPathCloseSegment(path);
|
|
RastShape shape = { .left = INT_MAX, .top = INT_MAX };
|
|
uintptr_t start = 0;
|
|
|
|
for (uintptr_t i = 0; i < RAST_ARRAY_LENGTH_U(path->segments); i++) {
|
|
_RastShapeAddEdges(&shape, &path->vertices[start], 1, false, path->segments[i].uptoVertex - start);
|
|
start = path->segments[i].uptoVertex;
|
|
}
|
|
|
|
return shape;
|
|
}
|
|
|
|
void _RastJoinMeter(RAST_ARRAY(RastVertex) *output, RastVertex *from1, RastVertex *to1, RastVertex *from2, RastVertex *to2, RastVertex *point, float miterLimit) {
|
|
// TODO For internal contours, this can generate vertices forming anticlockwise regions if the contour width is large enough.
|
|
// (You can't see it though because we use a non-zero fill rule.)
|
|
|
|
if ((to1->x - from2->x) * (to1->x - from2->x) + (to1->y - from2->y) * (to1->y - from2->y) < RAST_ADD_VERTEX_MINIMUM_DISTANCE_SQUARED) {
|
|
RAST_ARRAY_ADD(*output, *from2);
|
|
} else {
|
|
float a = to1->x - from1->x, b = from2->x - to2->x, e = from2->x - from1->x;
|
|
float c = to1->y - from1->y, d = from2->y - to2->y, f = from2->y - from1->y;
|
|
float j = (d * e - b * f) / (d * a - b * c);
|
|
RastVertex v = { from1->x + j * (to1->x - from1->x), from1->y + j * (to1->y - from1->y) };
|
|
|
|
if ((v.x - point->x) * (v.x - point->x) + (v.y - point->y) * (v.y - point->y) <= miterLimit * miterLimit) {
|
|
RAST_ARRAY_ADD(*output, v);
|
|
} else {
|
|
// TODO Move bevel to miter limit.
|
|
RAST_ARRAY_ADD(*output, *to1);
|
|
RAST_ARRAY_ADD(*output, *from2);
|
|
}
|
|
}
|
|
}
|
|
|
|
void _RastJoinArc(RAST_ARRAY(RastVertex) *output, float fromAngle, float toAngle, int divisions, RastVertex *point, float width) {
|
|
for (int j = 0; j < divisions; j++) {
|
|
float angle = fromAngle + j * (toAngle - fromAngle) / (divisions - 1);
|
|
RastVertex v = { point->x + EsCRTcosf(angle) * width, point->y + EsCRTsinf(angle) * width };
|
|
|
|
if (j == 0 || j == divisions - 1) {
|
|
RAST_ARRAY_ADD(*output, v);
|
|
continue;
|
|
}
|
|
|
|
RastVertex l = RAST_ARRAY_LAST(*output);
|
|
int dx = v.x - l.x, dy = v.y - l.y;
|
|
|
|
if (dx * dx + dy * dy > RAST_ADD_VERTEX_MINIMUM_DISTANCE_SQUARED) {
|
|
RAST_ARRAY_ADD(*output, v);
|
|
}
|
|
}
|
|
}
|
|
|
|
void _RastJoinRound(RAST_ARRAY(RastVertex) *output, RastVertex *from1, RastVertex *to1, RastVertex *from2,
|
|
RastVertex *to2, RastVertex *point, float width, int divisions, bool internal) {
|
|
float fromAngle = EsCRTatan2f(to1->y - point->y, to1->x - point->x);
|
|
float toAngle = EsCRTatan2f(from2->y - point->y, from2->x - point->x);
|
|
|
|
if (toAngle > fromAngle) {
|
|
toAngle -= 6.2831853071f;
|
|
}
|
|
|
|
if (internal == (fromAngle - toAngle < 3.141592653f)) {
|
|
_RastJoinMeter(output, from1, to1, from2, to2, point, ES_INFINITY);
|
|
return;
|
|
}
|
|
|
|
if (internal) {
|
|
toAngle += 6.2831853071f;
|
|
}
|
|
|
|
_RastJoinArc(output, fromAngle, toAngle, divisions, point, width);
|
|
}
|
|
|
|
void _RastShapeCreateContour(RastShape *shape, RastVertex *vertices, size_t vertexCount, RastContourStyle style, bool open) {
|
|
if (vertexCount < 2) {
|
|
return;
|
|
}
|
|
|
|
if (!open) {
|
|
RastVertex first = vertices[0], last = vertices[vertexCount - 1];
|
|
float dx = first.x - last.x, dy = first.y - last.y;
|
|
|
|
if (dx * dx + dy * dy < RAST_ADD_VERTEX_MINIMUM_DISTANCE_SQUARED) {
|
|
vertexCount--;
|
|
}
|
|
}
|
|
|
|
if (vertexCount <= 1) {
|
|
return;
|
|
} else if (vertexCount == 2) {
|
|
open = true;
|
|
}
|
|
|
|
RastVertex cap1, cap2, cap3, cap4;
|
|
|
|
RAST_ARRAY(RastVertex) path1 = { 0 };
|
|
RAST_ARRAY(RastVertex) path2 = { 0 };
|
|
|
|
for (int internal = 0; internal < 2; internal++) {
|
|
float width = internal ? style.internalWidth : style.externalWidth;
|
|
RastVertex *capStart = internal ? &cap4 : &cap1;
|
|
RastVertex *capEnd = internal ? &cap3 : &cap2;
|
|
|
|
for (uintptr_t i = 0; i < vertexCount; i++) {
|
|
RastVertex *from = &vertices[(i + 0) % vertexCount],
|
|
*to = &vertices[(i + 1) % vertexCount];
|
|
float dx = to->x - from->x, dy = to->y - from->y;
|
|
float scale = (internal ? -width : width) / EsCRTsqrtf(dx * dx + dy * dy);
|
|
float ox = -dy * scale, oy = dx * scale;
|
|
RastVertex cf = { from->x + ox, from->y + oy };
|
|
RastVertex ct = { to->x + ox, to->y + oy };
|
|
RAST_ARRAY_ADD(path1, cf);
|
|
RAST_ARRAY_ADD(path1, ct);
|
|
}
|
|
|
|
if (open) RAST_ARRAY_ADD(path2, (*capStart = path1[0]));
|
|
|
|
if (style.joinMode == RAST_LINE_JOIN_MITER) {
|
|
for (int i = 0; i < RAST_ARRAY_LENGTH(path1) - (open ? 4 : 0); i += 2) {
|
|
RastVertex *p1 = &vertices[(i / 2 + 0) % vertexCount],
|
|
*p2 = &vertices[(i / 2 + 1) % vertexCount],
|
|
*p3 = &vertices[(i / 2 + 2) % vertexCount];
|
|
float cross = (p2->x - p1->x) * (p3->y - p2->y) - (p2->y - p1->y) * (p3->x - p2->x);
|
|
|
|
// TODO Use style.miterLimit + width?
|
|
_RastJoinMeter(&path2, &path1[i], &path1[i + 1], &path1[(i + 2) % RAST_ARRAY_LENGTH(path1)],
|
|
&path1[(i + 3) % RAST_ARRAY_LENGTH(path1)], &vertices[(i / 2 + 1) % vertexCount],
|
|
(internal ? (cross > 0) : (cross < 0)) ? style.miterLimit : ES_INFINITY);
|
|
}
|
|
} else if (style.joinMode == RAST_LINE_JOIN_ROUND) {
|
|
int divisions = EsCRTceilf(3.14159265358f * 0.5f / EsCRTacosf(width / (width + RAST_ROUND_TOLERANCE))) + 1;
|
|
|
|
for (int i = 0; i < RAST_ARRAY_LENGTH(path1) - (open ? 4 : 0); i += 2) {
|
|
_RastJoinRound(&path2, &path1[i], &path1[i + 1], &path1[(i + 2) % RAST_ARRAY_LENGTH(path1)],
|
|
&path1[(i + 3) % RAST_ARRAY_LENGTH(path1)], &vertices[(i / 2 + 1) % vertexCount], width, divisions, internal);
|
|
}
|
|
}
|
|
|
|
if (open) RAST_ARRAY_ADD(path2, (*capEnd = path1[RAST_ARRAY_LENGTH(path1) - 3]));
|
|
|
|
_RastShapeAddEdges(shape, &path2[0], internal ? -1 : 1, open, RAST_ARRAY_LENGTH(path2));
|
|
|
|
RAST_ARRAY_CLEAR(path1);
|
|
RAST_ARRAY_CLEAR(path2);
|
|
}
|
|
|
|
if (open) {
|
|
// TODO Cap styles don't work if the contour is not centered (i.e. internalWidth != externalWidth).
|
|
|
|
for (int i = 0; i < 2; i++) {
|
|
RastVertex c = i ? cap4 : cap2, d = i ? cap1 : cap3;
|
|
RastVertex *from = &vertices[(i ? 1 : (vertexCount - 2))];
|
|
RastVertex *to = &vertices[(i ? 0 : (vertexCount - 1))];
|
|
RAST_ARRAY_ADD(path1, c);
|
|
|
|
if (style.capMode == RAST_LINE_CAP_SQUARE) {
|
|
float dx = to->x - from->x, dy = to->y - from->y;
|
|
float scale = 0.5f * (style.internalWidth + style.externalWidth) / EsCRTsqrtf(dx * dx + dy * dy);
|
|
RastVertex c0 = { .x = c.x + scale * dx, .y = c.y + scale * dy };
|
|
RastVertex d0 = { .x = d.x + scale * dx, .y = d.y + scale * dy };
|
|
RAST_ARRAY_ADD(path1, c0);
|
|
RAST_ARRAY_ADD(path1, d0);
|
|
} else if (style.capMode == RAST_LINE_CAP_ROUND) {
|
|
float angle = EsCRTatan2f(d.y - to->y, d.x - to->x);
|
|
float width = 0.5f * (style.internalWidth + style.externalWidth);
|
|
int divisions = EsCRTceilf(3.14159265358f * 0.5f / EsCRTacosf(width / (width + RAST_ROUND_TOLERANCE))) + 1;
|
|
_RastJoinArc(&path1, angle + 3.14159265358f, angle, divisions, to, width);
|
|
}
|
|
|
|
RAST_ARRAY_ADD(path1, d);
|
|
_RastShapeAddEdges(shape, &path1[0], 1, true, RAST_ARRAY_LENGTH(path1));
|
|
RAST_ARRAY_CLEAR(path1);
|
|
}
|
|
}
|
|
|
|
RAST_ARRAY_FREE(path1);
|
|
RAST_ARRAY_FREE(path2);
|
|
}
|
|
|
|
RastShape RastShapeCreateContour(RastPath *path, RastContourStyle style, bool open) {
|
|
if (RAST_ARRAY_LENGTH(path->vertices) < 2) return (RastShape) { 0 };
|
|
|
|
RastShape shape = { .left = INT_MAX, .top = INT_MAX };
|
|
RastPathCloseSegment(path);
|
|
|
|
for (uintptr_t i = 0; i < RAST_ARRAY_LENGTH(path->segments); i++) {
|
|
uintptr_t first = i ? path->segments[i - 1].uptoVertex : 0;
|
|
_RastShapeCreateContour(&shape, &path->vertices[first], path->segments[i].uptoVertex - first, style, open);
|
|
}
|
|
|
|
if (RAST_ARRAY_LENGTH(shape.edges) == 0) return (RastShape) { 0 };
|
|
return shape;
|
|
}
|
|
|
|
void _RastPathAddVertex(RastPath *path, RastVertex vertex) {
|
|
if (RAST_ARRAY_LENGTH(path->vertices)) {
|
|
RastVertex last = RAST_ARRAY_LAST(path->vertices);
|
|
float dx = last.x - vertex.x, dy = last.y - vertex.y;
|
|
|
|
if (dx * dx + dy * dy < RAST_ADD_VERTEX_MINIMUM_DISTANCE_SQUARED) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
RAST_ARRAY_ADD(path->vertices, vertex);
|
|
}
|
|
|
|
RastShape RastShapeCreateDashed(RastPath *path, RastDash *dashStyles, size_t dashStyleCount, bool open) {
|
|
RAST_ARRAY(RastVertex) vertices = path->vertices;
|
|
size_t vertexCount = RAST_ARRAY_LENGTH(vertices);
|
|
|
|
if (dashStyleCount < 1 || vertexCount < 2) {
|
|
return (RastShape) { 0 };
|
|
}
|
|
|
|
RastDash *style = dashStyles + 0;
|
|
RastVertex from = vertices[0];
|
|
RastVertex *to = &vertices[1];
|
|
RastPath dash = {};
|
|
RastShape shape = { .left = INT_MAX, .top = INT_MAX };
|
|
|
|
if (!open) {
|
|
from = RAST_ARRAY_LAST(vertices);
|
|
to = &vertices[0];
|
|
}
|
|
|
|
while (to != &vertices[vertexCount]) {
|
|
float accumulatedLength = 0;
|
|
|
|
_RastPathAddVertex(&dash, from);
|
|
|
|
while (to != &vertices[vertexCount]) {
|
|
float dx = to->x - from.x, dy = to->y - from.y;
|
|
float distance = EsCRTsqrtf(dx * dx + dy * dy);
|
|
|
|
if (accumulatedLength + distance >= style->length) {
|
|
float fraction = (style->length - accumulatedLength) / distance;
|
|
RastVertex stop = { from.x + fraction * dx, from.y + fraction * dy };
|
|
_RastPathAddVertex(&dash, stop);
|
|
from = stop;
|
|
break;
|
|
}
|
|
|
|
accumulatedLength += distance;
|
|
from = *to;
|
|
_RastPathAddVertex(&dash, from);
|
|
to++;
|
|
}
|
|
|
|
_RastShapeCreateContour(&shape, &dash.vertices[0], RAST_ARRAY_LENGTH(dash.vertices), *style->style, true);
|
|
|
|
accumulatedLength = 0;
|
|
|
|
while (to != &vertices[vertexCount]) {
|
|
float dx = to->x - from.x, dy = to->y - from.y;
|
|
float distance = EsCRTsqrtf(dx * dx + dy * dy);
|
|
|
|
if (accumulatedLength + distance >= style->gap) {
|
|
float fraction = (style->gap - accumulatedLength) / distance;
|
|
RastVertex stop = { from.x + fraction * dx, from.y + fraction * dy };
|
|
from = stop;
|
|
break;
|
|
}
|
|
|
|
accumulatedLength += distance;
|
|
from = *to;
|
|
to++;
|
|
}
|
|
|
|
style++;
|
|
|
|
if (style == dashStyles + dashStyleCount) {
|
|
style = dashStyles + 0;
|
|
}
|
|
|
|
RAST_ARRAY_CLEAR(dash.vertices);
|
|
}
|
|
|
|
return shape;
|
|
}
|
|
|
|
RastVertex _RastVertexScale(RastVertex vertex, RastVertex scale) {
|
|
return (RastVertex) { vertex.x * scale.x, vertex.y * scale.y };
|
|
}
|
|
|
|
void _RastFlattenBezierRecursive(RastPath *path, RastVertex v1, RastVertex v2, RastVertex v3, RastVertex v4, int level) {
|
|
if (level > 8) return;
|
|
|
|
RastVertex v12 = RAST_AVERAGE_VERTICES(v1, v2);
|
|
RastVertex v23 = RAST_AVERAGE_VERTICES(v2, v3);
|
|
RastVertex v34 = RAST_AVERAGE_VERTICES(v3, v4);
|
|
|
|
RastVertex delta = { v4.x - v1.x, v4.y - v1.y };
|
|
float d = AbsoluteFloat((v2.x - v4.x) * delta.y + (v4.y - v2.y) * delta.x)
|
|
+ AbsoluteFloat((v3.x - v4.x) * delta.y + (v4.y - v3.y) * delta.x);
|
|
|
|
if (d * d < RAST_FLATTEN_TOLERANCE * (delta.x * delta.x + delta.y * delta.y)) {
|
|
_RastPathAddVertex(path, v4);
|
|
return;
|
|
}
|
|
|
|
RastVertex v123 = RAST_AVERAGE_VERTICES(v12, v23);
|
|
RastVertex v234 = RAST_AVERAGE_VERTICES(v23, v34);
|
|
RastVertex v1234 = RAST_AVERAGE_VERTICES(v123, v234);
|
|
|
|
_RastFlattenBezierRecursive(path, v1, v12, v123, v1234, level + 1);
|
|
_RastFlattenBezierRecursive(path, v1234, v234, v34, v4, level + 1);
|
|
}
|
|
|
|
void RastPathAppendBezier(RastPath *path, const RastVertex *vertices, size_t vertexCount, RastVertex scale) {
|
|
if (vertexCount < 4) return;
|
|
_RastPathAddVertex(path, _RastVertexScale(vertices[0], scale));
|
|
|
|
for (uintptr_t i = 0; i < vertexCount - 3; i += 3) {
|
|
// TODO Scale the control points such that the center of the curve is aligned?
|
|
_RastFlattenBezierRecursive(path, _RastVertexScale(vertices[i + 0], scale), _RastVertexScale(vertices[i + 1], scale),
|
|
_RastVertexScale(vertices[i + 2], scale), _RastVertexScale(vertices[i + 3], scale), 0);
|
|
}
|
|
}
|
|
|
|
void RastPathAppendLinear(RastPath *path, RastVertex *vertices, size_t vertexCount, RastVertex scale) {
|
|
if (!vertexCount) return;
|
|
|
|
for (uintptr_t i = 0; i < vertexCount; i++) {
|
|
_RastPathAddVertex(path, _RastVertexScale(vertices[i], scale));
|
|
}
|
|
}
|
|
|
|
void RastPathAppendArc(RastPath *path, RastVertex center, float radius, float startAngle, float endAngle) {
|
|
float deltaAngle = EsCRTacosf(1 - 0.5f * RAST_FLATTEN_TOLERANCE * RAST_FLATTEN_TOLERANCE / radius / radius); // From cosine rule.
|
|
size_t steps = EsCRTfabsf(endAngle - startAngle) / deltaAngle;
|
|
|
|
for (uintptr_t i = 0; i <= steps; i++) {
|
|
float angle = (endAngle - startAngle) / steps * i + startAngle;
|
|
RastVertex vertex;
|
|
vertex.x = center.x + radius * EsCRTcosf(angle);
|
|
vertex.y = center.y + radius * EsCRTsinf(angle);
|
|
_RastPathAddVertex(path, vertex);
|
|
}
|
|
}
|
|
|
|
void RastPathTranslate(RastPath *path, float x, float y) {
|
|
if (!x && !y) return;
|
|
|
|
for (uintptr_t i = 0; i < RAST_ARRAY_LENGTH_U(path->vertices); i++) {
|
|
path->vertices[i].x += x;
|
|
path->vertices[i].y += y;
|
|
}
|
|
}
|
|
|
|
void RastPathTransform(RastPath *path, float *matrix) {
|
|
for (uintptr_t i = 0; i < RAST_ARRAY_LENGTH_U(path->vertices); i++) {
|
|
float x = path->vertices[i].x, y = path->vertices[i].y;
|
|
path->vertices[i].x = matrix[0] * x + matrix[1] * y + matrix[2];
|
|
path->vertices[i].y = matrix[3] * x + matrix[4] * y + matrix[5];
|
|
}
|
|
}
|
|
|
|
void RastPathDestroy(RastPath *path) {
|
|
RAST_ARRAY_FREE(path->segments);
|
|
RAST_ARRAY_FREE(path->vertices);
|
|
}
|
|
|
|
float _RastInterpolateWithGamma(float from, float to, float progress) {
|
|
from = from * from;
|
|
to = to * to;
|
|
return EsCRTsqrtf(from + progress * (to - from));
|
|
}
|
|
|
|
float _RastInterpolateSimple(float from, float to, float progress) {
|
|
return from + progress * (to - from);
|
|
}
|
|
|
|
void RastGradientInitialise(RastPaint *paint, RastGradientStop *stops, size_t stopCount, bool useGammaInterpolation) {
|
|
if (!stopCount) {
|
|
return;
|
|
}
|
|
|
|
paint->gradient.color = (uint32_t *) EsHeapAllocate(4 * RAST_GRADIENT_COLORS, false);
|
|
paint->gradient.alpha = (float *) EsHeapAllocate(sizeof(float) * RAST_GRADIENT_COLORS, false);
|
|
|
|
if (!paint->gradient.color || !paint->gradient.alpha) {
|
|
return;
|
|
}
|
|
|
|
for (uintptr_t stop = 0; stop < stopCount - 1; stop++) {
|
|
float fa = ((stops[stop + 0].color >> 24) & 0xFF) / 255.0f;
|
|
float fb = ((stops[stop + 0].color >> 16) & 0xFF) / 255.0f;
|
|
float fg = ((stops[stop + 0].color >> 8) & 0xFF) / 255.0f;
|
|
float fr = ((stops[stop + 0].color >> 0) & 0xFF) / 255.0f;
|
|
float ta = ((stops[stop + 1].color >> 24) & 0xFF) / 255.0f;
|
|
float tb = ((stops[stop + 1].color >> 16) & 0xFF) / 255.0f;
|
|
float tg = ((stops[stop + 1].color >> 8) & 0xFF) / 255.0f;
|
|
float tr = ((stops[stop + 1].color >> 0) & 0xFF) / 255.0f;
|
|
|
|
int fi = RAST_GRADIENT_COLORS * (stop == 0 ? 0 : stops[stop + 0].position);
|
|
int ti = RAST_GRADIENT_COLORS * (stop == stopCount - 2 ? 1 : stops[stop + 1].position);
|
|
if (fi < 0) fi = 0;
|
|
if (ti > RAST_GRADIENT_COLORS) ti = RAST_GRADIENT_COLORS;
|
|
|
|
for (int i = fi; i < ti; i++) {
|
|
float p = (float) (i - fi) / (ti - fi);
|
|
paint->gradient.alpha[i] = fa + (ta - fa) * p;
|
|
|
|
if (useGammaInterpolation) {
|
|
paint->gradient.color[i] = (uint32_t) (_RastInterpolateWithGamma(fr, tr, p) * 255.0f) << 0
|
|
| (uint32_t) (_RastInterpolateWithGamma(fg, tg, p) * 255.0f) << 8
|
|
| (uint32_t) (_RastInterpolateWithGamma(fb, tb, p) * 255.0f) << 16;
|
|
} else {
|
|
paint->gradient.color[i] = (uint32_t) (_RastInterpolateSimple(fr, tr, p) * 255.0f) << 0
|
|
| (uint32_t) (_RastInterpolateSimple(fg, tg, p) * 255.0f) << 8
|
|
| (uint32_t) (_RastInterpolateSimple(fb, tb, p) * 255.0f) << 16;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void RastGradientDestroy(RastPaint *paint) {
|
|
if (paint->type == RAST_PAINT_LINEAR_GRADIENT
|
|
|| paint->type == RAST_PAINT_RADIAL_GRADIENT
|
|
|| paint->type == RAST_PAINT_ANGULAR_GRADIENT) {
|
|
EsHeapFree(paint->gradient.color);
|
|
EsHeapFree(paint->gradient.alpha);
|
|
}
|
|
}
|
|
|
|
#ifndef IN_DESIGNER
|
|
void EsDrawLine(EsPainter *painter, const float *vertices, size_t vertexCount, uint32_t color, float width, uint32_t flags) {
|
|
RastSurface surface = {};
|
|
surface.buffer = (uint32_t *) painter->target->bits;
|
|
surface.stride = painter->target->stride;
|
|
|
|
if (RastSurfaceInitialise(&surface, painter->target->width, painter->target->height, true)) {
|
|
RastPath path = {};
|
|
RastPathAppendLinear(&path, (RastVertex *) vertices, vertexCount, { 1, 1 });
|
|
RastContourStyle style = {};
|
|
style.externalWidth = width / 2.0f;
|
|
style.internalWidth = width / 2.0f;
|
|
style.capMode = (flags & ES_DRAW_LINE_CAP_ROUND) ? RAST_LINE_CAP_ROUND
|
|
: (flags & ES_DRAW_LINE_CAP_SQUARE) ? RAST_LINE_CAP_SQUARE
|
|
: RAST_LINE_CAP_FLAT;
|
|
RastShape shape = RastShapeCreateContour(&path, style, true);
|
|
RastPaint paint = {};
|
|
paint.type = RAST_PAINT_SOLID;
|
|
paint.solid.color = color & 0xFFFFFF;
|
|
paint.solid.alpha = (color >> 24) / 255.0f;
|
|
RastSurfaceFill(surface, shape, paint, false);
|
|
RastPathDestroy(&path);
|
|
}
|
|
|
|
RastSurfaceDestroy(&surface);
|
|
}
|
|
#endif
|