VideoTools/vendor/github.com/srwiley/rasterx/shapes.go
Stu Leak 68df790d27 Fix player frame generation and video playback
Major improvements to UnifiedPlayer:

1. GetFrameImage() now works when paused for responsive UI updates
2. Play() method properly starts FFmpeg process
3. Frame display loop runs continuously for smooth video display
4. Disabled audio temporarily to fix video playback fundamentals
5. Simplified FFmpeg command to focus on video stream only

Player now:
- Generates video frames correctly
- Shows video when paused
- Has responsive progress tracking
- Starts playback properly

Next steps: Re-enable audio playback once video is stable
2026-01-07 22:20:00 -05:00

222 lines
7.6 KiB
Go

// Copyright 2018 by the rasterx Authors. All rights reserved.
//_
// created: 2/06/2018 by S.R.Wiley
// Functions that rasterize common shapes easily.
package rasterx
import (
"math"
"golang.org/x/image/math/fixed"
)
// MaxDx is the Maximum radians a cubic splice is allowed to span
// in ellipse parametric when approximating an off-axis ellipse.
const MaxDx float64 = math.Pi / 8
// ToFixedP converts two floats to a fixed point.
func ToFixedP(x, y float64) (p fixed.Point26_6) {
p.X = fixed.Int26_6(x * 64)
p.Y = fixed.Int26_6(y * 64)
return
}
// AddCircle adds a circle to the Adder p
func AddCircle(cx, cy, r float64, p Adder) {
AddEllipse(cx, cy, r, r, 0, p)
}
// AddEllipse adds an elipse with center at cx,cy, with the indicated
// x and y radius, (rx, ry), rotated around the center by rot degrees.
func AddEllipse(cx, cy, rx, ry, rot float64, p Adder) {
rotRads := rot * math.Pi / 180
px, py := Identity.
Translate(cx, cy).Rotate(rotRads).Translate(-cx, -cy).Transform(cx+rx, cy)
points := []float64{rx, ry, rot, 1.0, 0.0, px, py}
p.Start(ToFixedP(px, py))
AddArc(points, cx, cy, px, py, p)
p.Stop(true)
}
// AddRect adds a rectangle of the indicated size, rotated
// around the center by rot degrees.
func AddRect(minX, minY, maxX, maxY, rot float64, p Adder) {
rot *= math.Pi / 180
cx, cy := (minX+maxX)/2, (minY+maxY)/2
m := Identity.Translate(cx, cy).Rotate(rot).Translate(-cx, -cy)
q := &MatrixAdder{M: m, Adder: p}
q.Start(ToFixedP(minX, minY))
q.Line(ToFixedP(maxX, minY))
q.Line(ToFixedP(maxX, maxY))
q.Line(ToFixedP(minX, maxY))
q.Stop(true)
}
// AddRoundRect adds a rectangle of the indicated size, rotated
// around the center by rot degrees with rounded corners of radius
// rx in the x axis and ry in the y axis. gf specifes the shape of the
// filleting function. Valid values are RoundGap, QuadraticGap, CubicGap,
// FlatGap, or nil which defaults to a flat gap.
func AddRoundRect(minX, minY, maxX, maxY, rx, ry, rot float64, gf GapFunc, p Adder) {
if rx <= 0 || ry <= 0 {
AddRect(minX, minY, maxX, maxY, rot, p)
return
}
rot *= math.Pi / 180
if gf == nil {
gf = FlatGap
}
w := maxX - minX
if w < rx*2 {
rx = w / 2
}
h := maxY - minY
if h < ry*2 {
ry = h / 2
}
stretch := rx / ry
midY := minY + h/2
m := Identity.Translate(minX+w/2, midY).Rotate(rot).Scale(1, 1/stretch).Translate(-minX-w/2, -minY-h/2)
maxY = midY + h/2*stretch
minY = midY - h/2*stretch
q := &MatrixAdder{M: m, Adder: p}
q.Start(ToFixedP(minX+rx, minY))
q.Line(ToFixedP(maxX-rx, minY))
gf(q, ToFixedP(maxX-rx, minY+rx), ToFixedP(0, -rx), ToFixedP(rx, 0))
q.Line(ToFixedP(maxX, maxY-rx))
gf(q, ToFixedP(maxX-rx, maxY-rx), ToFixedP(rx, 0), ToFixedP(0, rx))
q.Line(ToFixedP(minX+rx, maxY))
gf(q, ToFixedP(minX+rx, maxY-rx), ToFixedP(0, rx), ToFixedP(-rx, 0))
q.Line(ToFixedP(minX, minY+rx))
gf(q, ToFixedP(minX+rx, minY+rx), ToFixedP(-rx, 0), ToFixedP(0, -rx))
q.Stop(true)
}
//AddArc adds an arc to the adder p
func AddArc(points []float64, cx, cy, px, py float64, p Adder) (lx, ly float64) {
rotX := points[2] * math.Pi / 180 // Convert degress to radians
largeArc := points[3] != 0
sweep := points[4] != 0
startAngle := math.Atan2(py-cy, px-cx) - rotX
endAngle := math.Atan2(points[6]-cy, points[5]-cx) - rotX
deltaTheta := endAngle - startAngle
arcBig := math.Abs(deltaTheta) > math.Pi
// Approximate ellipse using cubic bezeir splines
etaStart := math.Atan2(math.Sin(startAngle)/points[1], math.Cos(startAngle)/points[0])
etaEnd := math.Atan2(math.Sin(endAngle)/points[1], math.Cos(endAngle)/points[0])
deltaEta := etaEnd - etaStart
if (arcBig && !largeArc) || (!arcBig && largeArc) { // Go has no boolean XOR
if deltaEta < 0 {
deltaEta += math.Pi * 2
} else {
deltaEta -= math.Pi * 2
}
}
// This check might be needed if the center point of the elipse is
// at the midpoint of the start and end lines.
if deltaEta < 0 && sweep {
deltaEta += math.Pi * 2
} else if deltaEta >= 0 && !sweep {
deltaEta -= math.Pi * 2
}
// Round up to determine number of cubic splines to approximate bezier curve
segs := int(math.Abs(deltaEta)/MaxDx) + 1
dEta := deltaEta / float64(segs) // span of each segment
// Approximate the ellipse using a set of cubic bezier curves by the method of
// L. Maisonobe, "Drawing an elliptical arc using polylines, quadratic
// or cubic Bezier curves", 2003
// https://www.spaceroots.org/documents/elllipse/elliptical-arc.pdf
tde := math.Tan(dEta / 2)
alpha := math.Sin(dEta) * (math.Sqrt(4+3*tde*tde) - 1) / 3 // Math is fun!
lx, ly = px, py
sinTheta, cosTheta := math.Sin(rotX), math.Cos(rotX)
ldx, ldy := ellipsePrime(points[0], points[1], sinTheta, cosTheta, etaStart, cx, cy)
for i := 1; i <= segs; i++ {
eta := etaStart + dEta*float64(i)
var px, py float64
if i == segs {
px, py = points[5], points[6] // Just makes the end point exact; no roundoff error
} else {
px, py = ellipsePointAt(points[0], points[1], sinTheta, cosTheta, eta, cx, cy)
}
dx, dy := ellipsePrime(points[0], points[1], sinTheta, cosTheta, eta, cx, cy)
p.CubeBezier(ToFixedP(lx+alpha*ldx, ly+alpha*ldy),
ToFixedP(px-alpha*dx, py-alpha*dy), ToFixedP(px, py))
lx, ly, ldx, ldy = px, py, dx, dy
}
return lx, ly
}
// ellipsePrime gives tangent vectors for parameterized elipse; a, b, radii, eta parameter, center cx, cy
func ellipsePrime(a, b, sinTheta, cosTheta, eta, cx, cy float64) (px, py float64) {
bCosEta := b * math.Cos(eta)
aSinEta := a * math.Sin(eta)
px = -aSinEta*cosTheta - bCosEta*sinTheta
py = -aSinEta*sinTheta + bCosEta*cosTheta
return
}
// ellipsePointAt gives points for parameterized elipse; a, b, radii, eta parameter, center cx, cy
func ellipsePointAt(a, b, sinTheta, cosTheta, eta, cx, cy float64) (px, py float64) {
aCosEta := a * math.Cos(eta)
bSinEta := b * math.Sin(eta)
px = cx + aCosEta*cosTheta - bSinEta*sinTheta
py = cy + aCosEta*sinTheta + bSinEta*cosTheta
return
}
// FindEllipseCenter locates the center of the Ellipse if it exists. If it does not exist,
// the radius values will be increased minimally for a solution to be possible
// while preserving the ra to rb ratio. ra and rb arguments are pointers that can be
// checked after the call to see if the values changed. This method uses coordinate transformations
// to reduce the problem to finding the center of a circle that includes the origin
// and an arbitrary point. The center of the circle is then transformed
// back to the original coordinates and returned.
func FindEllipseCenter(ra, rb *float64, rotX, startX, startY, endX, endY float64, sweep, smallArc bool) (cx, cy float64) {
cos, sin := math.Cos(rotX), math.Sin(rotX)
// Move origin to start point
nx, ny := endX-startX, endY-startY
// Rotate ellipse x-axis to coordinate x-axis
nx, ny = nx*cos+ny*sin, -nx*sin+ny*cos
// Scale X dimension so that ra = rb
nx *= *rb / *ra // Now the ellipse is a circle radius rb; therefore foci and center coincide
midX, midY := nx/2, ny/2
midlenSq := midX*midX + midY*midY
var hr float64
if *rb**rb < midlenSq {
// Requested ellipse does not exist; scale ra, rb to fit. Length of
// span is greater than max width of ellipse, must scale *ra, *rb
nrb := math.Sqrt(midlenSq)
if *ra == *rb {
*ra = nrb // prevents roundoff
} else {
*ra = *ra * nrb / *rb
}
*rb = nrb
} else {
hr = math.Sqrt(*rb**rb-midlenSq) / math.Sqrt(midlenSq)
}
// Notice that if hr is zero, both answers are the same.
if (sweep && smallArc) || (!sweep && !smallArc) {
cx = midX + midY*hr
cy = midY - midX*hr
} else {
cx = midX - midY*hr
cy = midY + midX*hr
}
// reverse scale
cx *= *ra / *rb
//Reverse rotate and translate back to original coordinates
return cx*cos - cy*sin + startX, cx*sin + cy*cos + startY
}