Skip to content

Commit

Permalink
Merge pull request #45 from kepstin/cubic-ellipse
Browse files Browse the repository at this point in the history
Fix crashes in ellipse rendering
kepstin authored Jul 14, 2023
2 parents f9f3ae6 + a053049 commit c1673e9
Showing 5 changed files with 49 additions and 59 deletions.
4 changes: 2 additions & 2 deletions bbb_presentation_video/bindings/fontconfig.py
Original file line number Diff line number Diff line change
@@ -16,8 +16,8 @@ class FontconfigError(Exception):

def _FcBool_errcheck(
result: Optional[Type[ctypes._CData]],
_func: ctypes._FuncPointer,
_arguments: Tuple[ctypes._CData, ...],
_func: Any,
_arguments: Any,
) -> Any:
res = int(cast(c_int, result))
if res != 1:
21 changes: 4 additions & 17 deletions bbb_presentation_video/renderer/tldraw/shape/ellipse.py
Original file line number Diff line number Diff line change
@@ -29,6 +29,7 @@
get_perfect_dash_props,
perimeter_of_ellipse,
)
from bbb_presentation_video.renderer.utils import cairo_draw_ellipse


def draw_stroke_points(
@@ -37,8 +38,8 @@ def draw_stroke_points(
stroke_width = STROKE_WIDTHS[style.size]
random = Random(id)
variation = stroke_width * 2
rx = radius[0] + random.uniform(-variation, variation)
ry = radius[1] + random.uniform(-variation, variation)
rx = max(0, radius[0] + random.uniform(-variation, variation))
ry = max(0, radius[1] + random.uniform(-variation, variation))
perimeter = perimeter_of_ellipse(rx, ry)
points: List[Tuple[float, float, float]] = []
start = random.uniform(0, tau)
@@ -122,21 +123,7 @@ def dash_ellipse(ctx: cairo.Context[CairoSomeSurface], shape: EllipseShape) -> N
snap=4,
)

ctx.translate(radius[0], radius[1])
if radius[0] <= stroke_width / 2 or radius[1] <= stroke_width < 2:
# If radii are too small, draw line segments
ctx.move_to(-rx, 0)
ctx.line_to(0, -ry)
ctx.line_to(rx, 0)
ctx.line_to(0, ry)
ctx.close_path()
else:
ctx.save()
ctx.scale(rx, ry)
ctx.new_sub_path()
ctx.arc(0, 0, 1, 0, tau)
ctx.close_path()
ctx.restore()
cairo_draw_ellipse(ctx, radius[0], radius[1], radius[0], radius[1])

if style.isFilled:
ctx.set_source_rgb(fill.r, fill.g, fill.b)
7 changes: 7 additions & 0 deletions bbb_presentation_video/renderer/tldraw/utils.py
Original file line number Diff line number Diff line change
@@ -181,6 +181,13 @@ class Decoration(Enum):

def perimeter_of_ellipse(rx: float, ry: float) -> float:
"""Find the approximate perimeter of an ellipse."""

# Handle degenerate case where the "ellipse" is actually a line or a point
if rx == 0:
return 2 * ry
elif ry == 0:
return 2 * rx

h = (rx - ry) ** 2 / (rx + ry) ** 2
return pi * (rx + ry) * (1 + (3 * h) / (10 + sqrt(4 - 3 * h)))

34 changes: 34 additions & 0 deletions bbb_presentation_video/renderer/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from __future__ import annotations

from typing import TypeVar

import cairo

BEZIER_CIRCLE_MAGIC = 0.551915024494

CairoSomeSurface = TypeVar("CairoSomeSurface", bound=cairo.Surface)


def cairo_draw_ellipse(
ctx: cairo.Context[CairoSomeSurface], x: float, y: float, rx: float, ry: float
) -> None:
"""Draw a bezier approximation to an ellipse.
Cairo's arc function can only draw unit circles, and the need to do a transform
to scale them causes problems when drawing very thin circles. The 4-segment bezier
approximation to an ellipse is very close, so draw that instead.
:param x: The x coordinate of the center of the ellipse.
:param y: The y coordinate of the center of the ellipse.
:param rx: The horizontal radius of the ellipse.
:param ry: The vertical radius of the ellipse.
"""
ctx.save()
ctx.translate(x, y)
ctx.move_to(-rx, 0)
ctx.curve_to(-rx, -ry * BEZIER_CIRCLE_MAGIC, -rx * BEZIER_CIRCLE_MAGIC, -ry, 0, -ry)
ctx.curve_to(rx * BEZIER_CIRCLE_MAGIC, -ry, rx, -ry * BEZIER_CIRCLE_MAGIC, rx, 0)
ctx.curve_to(rx, ry * BEZIER_CIRCLE_MAGIC, rx * BEZIER_CIRCLE_MAGIC, ry, 0, ry)
ctx.curve_to(-rx * BEZIER_CIRCLE_MAGIC, ry, -rx, ry * BEZIER_CIRCLE_MAGIC, -rx, 0)
ctx.close_path()
ctx.restore()
42 changes: 2 additions & 40 deletions bbb_presentation_video/renderer/whiteboard.py
Original file line number Diff line number Diff line change
@@ -28,8 +28,7 @@
Transform,
apply_shapes_transform,
)

BEZIER_CIRCLE_MAGIC = 0.551915024494
from bbb_presentation_video.renderer.utils import cairo_draw_ellipse

FONT_FAMILY = "Arial"

@@ -382,44 +381,7 @@ def draw_ellipse(self, shape: ShapeEvent) -> None:
else:
y2 = y1 - width_r - width_r

# Draw a bezier approximation to the ellipse. Cairo's arc function
# doesn't deal well with degenerate (0-height/width) ellipses because
# of the scaling required.
ctx.translate((x1 + x2) / 2, (y1 + y2) / 2)
ctx.move_to(-width_r, 0)
ctx.curve_to(
-width_r,
-height_r * BEZIER_CIRCLE_MAGIC,
-width_r * BEZIER_CIRCLE_MAGIC,
-height_r,
0,
-height_r,
)
ctx.curve_to(
width_r * BEZIER_CIRCLE_MAGIC,
-height_r,
width_r,
-height_r * BEZIER_CIRCLE_MAGIC,
width_r,
0,
)
ctx.curve_to(
width_r,
height_r * BEZIER_CIRCLE_MAGIC,
width_r * BEZIER_CIRCLE_MAGIC,
height_r,
0,
height_r,
)
ctx.curve_to(
-width_r * BEZIER_CIRCLE_MAGIC,
height_r,
-width_r,
height_r * BEZIER_CIRCLE_MAGIC,
-width_r,
0,
)
ctx.close_path()
cairo_draw_ellipse(ctx, (x1 + x2) / 2, (y1 + y2) / 2, width_r, height_r)
ctx.stroke()

def draw_triangle(self, shape: ShapeEvent) -> None:

0 comments on commit c1673e9

Please sign in to comment.