CropOverlay
A drag-to-crop rectangle over a video frame.
Controlled, so a drag reports the rectangle it wants through onRectChange and draws whatever rect is next passed back in. A caller that drops the change draws a rectangle that does not move.
The overlay measures itself and builds its own CropFrame from output and contentScale, so a host cannot hand it a rectangle sized against a mismatched letterbox. Inside a dev.jordond.filmstrip.compose.ui.VideoStage the stage has measured that already, and the overload on VideoStageScope reads its letterbox rather than taking an output of its own.
A constraint that arrives after the rectangle was drawn corrects it once, through onRectChange, so a lock picked from a chip reaches the rectangle without waiting for the next drag.
val surface = rememberVideoSurfaceState(player)
var rect by remember(source) { mutableStateOf(NormalizedRect.Full) }
Box(Modifier.videoAspect(surface, VideoContentScale.Fit)) {
VideoSurface(player, Modifier.fillMaxSize())
CropOverlay(
rect = rect,
onRectChange = { rect = it },
output = surface.outputSize,
constraint = CropConstraint.FixedAspect(ratio = 1f, minWidth = CropOverlayDefaults.MinWidth),
)
}
// The rectangle addresses the frame the overlay is drawn over, so the preview under it is the
// uncropped one and the crop joins the edit on the way out of the tool.
val cropped = remember(source, rect) {
compositionOf {
clip(source)
effects { crop(rect) }
}
}Parameters
Where the crop currently sits, in the frame's own normalized space.
Called with the rectangle a gesture asks for, already constrained.
The frame size the composition outputs, which the video is letterboxed against.
Modifier for the overlay, which fills whatever it is placed in.
How the video fills the space the overlay measures.
What the gesture is allowed to produce.
What the overlay paints with.
How long a corner bracket's arms and an edge grip are drawn.
How wide a handle answers to a finger, which is wider than it is drawn.
Whether rule-of-thirds guides are drawn while a drag is in flight.
A drag-to-crop rectangle over the video a dev.jordond.filmstrip.compose.ui.VideoStage is showing.
Everything the Size form does, reading the stage's own letterbox instead of measuring for one. A host that has already been letterboxed has no honest output size left to hand over, only the shape it was letterboxed to, and the rectangle this authors addresses the frame the stage is drawing.
var rect by remember(source) { mutableStateOf(NormalizedRect.Full) }
var lock: AspectRatio? by remember { mutableStateOf(null) }
VideoStage(player = player, outputAspect = outputAspect, modifier = Modifier.padding(16.dp)) {
CropOverlay(
rect = rect,
onRectChange = { rect = it },
constraint = lock?.let { CropConstraint.lockedTo(it, aspect) } ?: CropOverlayDefaults.Constraint,
)
}
// The rectangle addresses the frame the overlay is drawn over, so the preview under it is the
// uncropped one and the crop joins the edit on the way out of the tool.
compositionOf {
clip(source)
effects { crop(rect) }
}Parameters
Where the crop currently sits, in the frame's own normalized space.
Called with the rectangle a gesture asks for, already constrained.
Modifier for the overlay, which fills the stage.
What the gesture is allowed to produce.
What the overlay paints with.
How long a corner bracket's arms and an edge grip are drawn.
How wide a handle answers to a finger, which is wider than it is drawn.
Whether rule-of-thirds guides are drawn while a drag is in flight.