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
222 lines
7.6 KiB
Go
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
|
|
}
|