Sebastian Dröge 2024-05-28 16:26:33 +03:00 committed by GStreamer Marge Bot
parent 2fe852166e
commit 8522c8a445
6 changed files with 359 additions and 31 deletions

View file

@ -2622,6 +2622,18 @@
"type": "GdkGLContext", "type": "GdkGLContext",
"writable": true "writable": true
}, },
"orientation": {
"blurb": "Orientation of the video frames",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "auto (0)",
"mutable": "null",
"readable": true,
"type": "GstGtk4PaintableSinkOrientation",
"writable": true
},
"scaling-filter": { "scaling-filter": {
"blurb": "Scaling filter to use for rendering", "blurb": "Scaling filter to use for rendering",
"conditionally-available": false, "conditionally-available": false,
@ -2647,6 +2659,56 @@
"writable": true "writable": true
} }
} }
},
"GstGtk4PaintableSinkOrientation": {
"kind": "enum",
"values": [
{
"desc": "Auto",
"name": "auto",
"value": "0"
},
{
"desc": "Rotate0",
"name": "rotate0",
"value": "1"
},
{
"desc": "Rotate90",
"name": "rotate90",
"value": "2"
},
{
"desc": "Rotate180",
"name": "rotate180",
"value": "3"
},
{
"desc": "Rotate270",
"name": "rotate270",
"value": "4"
},
{
"desc": "FlipRotate0",
"name": "flip-rotate0",
"value": "5"
},
{
"desc": "FlipRotate90",
"name": "flip-rotate90",
"value": "6"
},
{
"desc": "FlipRotate180",
"name": "flip-rotate180",
"value": "7"
},
{
"desc": "FlipRotate270",
"name": "flip-rotate270",
"value": "8"
}
]
} }
}, },
"package": "gst-plugin-gtk4", "package": "gst-plugin-gtk4",

View file

@ -19,6 +19,7 @@ use gst::glib;
mod sink; mod sink;
mod utils; mod utils;
pub use sink::frame::Orientation;
pub use sink::paintable::Paintable; pub use sink::paintable::Paintable;
pub use sink::PaintableSink; pub use sink::PaintableSink;
@ -28,6 +29,7 @@ fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
use gst::prelude::*; use gst::prelude::*;
sink::paintable::Paintable::static_type().mark_as_plugin_api(gst::PluginAPIFlags::empty()); sink::paintable::Paintable::static_type().mark_as_plugin_api(gst::PluginAPIFlags::empty());
sink::frame::Orientation::static_type().mark_as_plugin_api(gst::PluginAPIFlags::empty());
} }
#[cfg(not(feature = "gtk_v4_10"))] #[cfg(not(feature = "gtk_v4_10"))]

View file

@ -72,11 +72,15 @@ pub enum TextureCacheId {
#[derive(Debug)] #[derive(Debug)]
enum MappedFrame { enum MappedFrame {
SysMem(gst_video::VideoFrame<gst_video::video_frame::Readable>), SysMem {
frame: gst_video::VideoFrame<gst_video::video_frame::Readable>,
orientation: Orientation,
},
#[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))]
GL { GL {
frame: gst_gl::GLVideoFrame<gst_gl::gl_video_frame::Readable>, frame: gst_gl::GLVideoFrame<gst_gl::gl_video_frame::Readable>,
wrapped_context: gst_gl::GLContext, wrapped_context: gst_gl::GLContext,
orientation: Orientation,
}, },
#[cfg(all(target_os = "linux", feature = "dmabuf"))] #[cfg(all(target_os = "linux", feature = "dmabuf"))]
DmaBuf { DmaBuf {
@ -88,13 +92,14 @@ enum MappedFrame {
strides: [usize; 4], strides: [usize; 4],
width: u32, width: u32,
height: u32, height: u32,
orientation: Orientation,
}, },
} }
impl MappedFrame { impl MappedFrame {
fn buffer(&self) -> &gst::BufferRef { fn buffer(&self) -> &gst::BufferRef {
match self { match self {
MappedFrame::SysMem(frame) => frame.buffer(), MappedFrame::SysMem { frame, .. } => frame.buffer(),
#[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))]
MappedFrame::GL { frame, .. } => frame.buffer(), MappedFrame::GL { frame, .. } => frame.buffer(),
#[cfg(all(target_os = "linux", feature = "dmabuf"))] #[cfg(all(target_os = "linux", feature = "dmabuf"))]
@ -104,7 +109,7 @@ impl MappedFrame {
fn width(&self) -> u32 { fn width(&self) -> u32 {
match self { match self {
MappedFrame::SysMem(frame) => frame.width(), MappedFrame::SysMem { frame, .. } => frame.width(),
#[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))]
MappedFrame::GL { frame, .. } => frame.width(), MappedFrame::GL { frame, .. } => frame.width(),
#[cfg(all(target_os = "linux", feature = "dmabuf"))] #[cfg(all(target_os = "linux", feature = "dmabuf"))]
@ -114,7 +119,7 @@ impl MappedFrame {
fn height(&self) -> u32 { fn height(&self) -> u32 {
match self { match self {
MappedFrame::SysMem(frame) => frame.height(), MappedFrame::SysMem { frame, .. } => frame.height(),
#[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))]
MappedFrame::GL { frame, .. } => frame.height(), MappedFrame::GL { frame, .. } => frame.height(),
#[cfg(all(target_os = "linux", feature = "dmabuf"))] #[cfg(all(target_os = "linux", feature = "dmabuf"))]
@ -124,13 +129,23 @@ impl MappedFrame {
fn format_info(&self) -> gst_video::VideoFormatInfo { fn format_info(&self) -> gst_video::VideoFormatInfo {
match self { match self {
MappedFrame::SysMem(frame) => frame.format_info(), MappedFrame::SysMem { frame, .. } => frame.format_info(),
#[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))]
MappedFrame::GL { frame, .. } => frame.format_info(), MappedFrame::GL { frame, .. } => frame.format_info(),
#[cfg(all(target_os = "linux", feature = "dmabuf"))] #[cfg(all(target_os = "linux", feature = "dmabuf"))]
MappedFrame::DmaBuf { info, .. } => info.format_info(), MappedFrame::DmaBuf { info, .. } => info.format_info(),
} }
} }
fn orientation(&self) -> Orientation {
match self {
MappedFrame::SysMem { orientation, .. } => *orientation,
#[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))]
MappedFrame::GL { orientation, .. } => *orientation,
#[cfg(all(target_os = "linux", feature = "dmabuf"))]
MappedFrame::DmaBuf { orientation, .. } => *orientation,
}
}
} }
#[derive(Debug)] #[derive(Debug)]
@ -139,6 +154,52 @@ pub(crate) struct Frame {
overlays: Vec<Overlay>, overlays: Vec<Overlay>,
} }
#[derive(Debug, Default, glib::Enum, PartialEq, Eq, Copy, Clone)]
#[repr(C)]
#[enum_type(name = "GstGtk4PaintableSinkOrientation")]
pub enum Orientation {
#[default]
Auto,
Rotate0,
Rotate90,
Rotate180,
Rotate270,
FlipRotate0,
FlipRotate90,
FlipRotate180,
FlipRotate270,
}
impl Orientation {
pub fn from_tags(tags: &gst::TagListRef) -> Option<Orientation> {
let orientation = tags
.generic("image-orientation")
.and_then(|v| v.get::<String>().ok())?;
Some(match orientation.as_str() {
"rotate-0" => Orientation::Rotate0,
"rotate-90" => Orientation::Rotate90,
"rotate-180" => Orientation::Rotate180,
"rotate-270" => Orientation::Rotate270,
"flip-rotate-0" => Orientation::FlipRotate0,
"flip-rotate-90" => Orientation::FlipRotate90,
"flip-rotate-180" => Orientation::FlipRotate180,
"flip-rotate-270" => Orientation::FlipRotate270,
_ => return None,
})
}
pub fn is_flip_width_height(self) -> bool {
matches!(
self,
Orientation::Rotate90
| Orientation::Rotate270
| Orientation::FlipRotate90
| Orientation::FlipRotate270
)
}
}
#[derive(Debug)] #[derive(Debug)]
struct Overlay { struct Overlay {
frame: gst_video::VideoFrame<gst_video::video_frame::Readable>, frame: gst_video::VideoFrame<gst_video::video_frame::Readable>,
@ -158,6 +219,7 @@ pub(crate) struct Texture {
pub height: f32, pub height: f32,
pub global_alpha: f32, pub global_alpha: f32,
pub has_alpha: bool, pub has_alpha: bool,
pub orientation: Orientation,
} }
struct FrameWrapper(gst_video::VideoFrame<gst_video::video_frame::Readable>); struct FrameWrapper(gst_video::VideoFrame<gst_video::video_frame::Readable>);
@ -374,14 +436,16 @@ impl Frame {
let width = self.frame.width(); let width = self.frame.width();
let height = self.frame.height(); let height = self.frame.height();
let has_alpha = self.frame.format_info().has_alpha(); let has_alpha = self.frame.format_info().has_alpha();
let orientation = self.frame.orientation();
let (texture, pixel_aspect_ratio) = match self.frame { let (texture, pixel_aspect_ratio) = match self.frame {
MappedFrame::SysMem(frame) => { MappedFrame::SysMem { frame, .. } => {
video_frame_to_memory_texture(frame, cached_textures, &mut used_textures) video_frame_to_memory_texture(frame, cached_textures, &mut used_textures)
} }
#[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))]
MappedFrame::GL { MappedFrame::GL {
frame, frame,
wrapped_context, wrapped_context,
..
} => { } => {
let Some(gdk_context) = gdk_context else { let Some(gdk_context) = gdk_context else {
// This will fail badly if the video frame was actually mapped as GL texture // This will fail badly if the video frame was actually mapped as GL texture
@ -407,6 +471,7 @@ impl Frame {
strides, strides,
width, width,
height, height,
..
} => video_frame_to_dmabuf_texture( } => video_frame_to_dmabuf_texture(
buffer, buffer,
cached_textures, cached_textures,
@ -429,6 +494,7 @@ impl Frame {
height: height as f32, height: height as f32,
global_alpha: 1.0, global_alpha: 1.0,
has_alpha, has_alpha,
orientation,
}); });
for overlay in self.overlays { for overlay in self.overlays {
@ -444,6 +510,7 @@ impl Frame {
height: overlay.height as f32, height: overlay.height as f32,
global_alpha: overlay.global_alpha, global_alpha: overlay.global_alpha,
has_alpha, has_alpha,
orientation: Orientation::Rotate0,
}); });
} }
@ -458,6 +525,7 @@ impl Frame {
pub(crate) fn new( pub(crate) fn new(
buffer: &gst::Buffer, buffer: &gst::Buffer,
info: &VideoInfo, info: &VideoInfo,
orientation: Orientation,
#[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] wrapped_context: Option< #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] wrapped_context: Option<
&gst_gl::GLContext, &gst_gl::GLContext,
>, >,
@ -521,6 +589,7 @@ impl Frame {
strides, strides,
width: vmeta.width(), width: vmeta.width(),
height: vmeta.height(), height: vmeta.height(),
orientation,
}); });
} }
} }
@ -571,6 +640,7 @@ impl Frame {
frame = Some(MappedFrame::GL { frame = Some(MappedFrame::GL {
frame: mapped_frame, frame: mapped_frame,
wrapped_context: wrapped_context.unwrap().clone(), wrapped_context: wrapped_context.unwrap().clone(),
orientation,
}); });
} }
} }
@ -580,10 +650,11 @@ impl Frame {
let mut frame = Self { let mut frame = Self {
frame: match frame { frame: match frame {
Some(frame) => frame, Some(frame) => frame,
None => MappedFrame::SysMem( None => MappedFrame::SysMem {
gst_video::VideoFrame::from_buffer_readable(buffer.clone(), info) frame: gst_video::VideoFrame::from_buffer_readable(buffer.clone(), info)
.map_err(|_| gst::FlowError::Error)?, .map_err(|_| gst::FlowError::Error)?,
), orientation,
},
}, },
overlays: vec![], overlays: vec![],
}; };

View file

@ -9,7 +9,7 @@
// //
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
use super::SinkEvent; use super::{frame, SinkEvent};
use crate::sink::frame::Frame; use crate::sink::frame::Frame;
use crate::sink::paintable::Paintable; use crate::sink::paintable::Paintable;
@ -58,11 +58,29 @@ pub(crate) static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
) )
}); });
struct StreamConfig {
info: Option<super::frame::VideoInfo>,
/// Orientation from a global scope tag
global_orientation: frame::Orientation,
/// Orientation from a stream scope tag
stream_orientation: Option<frame::Orientation>,
}
impl Default for StreamConfig {
fn default() -> Self {
StreamConfig {
info: None,
global_orientation: frame::Orientation::Rotate0,
stream_orientation: None,
}
}
}
#[derive(Default)] #[derive(Default)]
pub struct PaintableSink { pub struct PaintableSink {
paintable: Mutex<Option<ThreadGuard<Paintable>>>, paintable: Mutex<Option<ThreadGuard<Paintable>>>,
window: Mutex<Option<ThreadGuard<gtk::Window>>>, window: Mutex<Option<ThreadGuard<gtk::Window>>>,
info: Mutex<Option<super::frame::VideoInfo>>, config: Mutex<StreamConfig>,
sender: Mutex<Option<async_channel::Sender<SinkEvent>>>, sender: Mutex<Option<async_channel::Sender<SinkEvent>>>,
pending_frame: Mutex<Option<Frame>>, pending_frame: Mutex<Option<Frame>>,
cached_caps: Mutex<Option<gst::Caps>>, cached_caps: Mutex<Option<gst::Caps>>,
@ -376,7 +394,7 @@ impl ElementImpl for PaintableSink {
match transition { match transition {
gst::StateChange::PausedToReady => { gst::StateChange::PausedToReady => {
let _ = self.info.lock().unwrap().take(); *self.config.lock().unwrap() = StreamConfig::default();
let _ = self.pending_frame.lock().unwrap().take(); let _ = self.pending_frame.lock().unwrap().take();
// Flush frames from the GDK paintable but don't wait // Flush frames from the GDK paintable but don't wait
@ -455,7 +473,7 @@ impl BaseSinkImpl for PaintableSink {
.into(), .into(),
}; };
self.info.lock().unwrap().replace(video_info); self.config.lock().unwrap().info = Some(video_info);
Ok(()) Ok(())
} }
@ -526,6 +544,31 @@ impl BaseSinkImpl for PaintableSink {
_ => BaseSinkImplExt::parent_query(self, query), _ => BaseSinkImplExt::parent_query(self, query),
} }
} }
fn event(&self, event: gst::Event) -> bool {
match event.view() {
gst::EventView::StreamStart(_) => {
let mut config = self.config.lock().unwrap();
config.global_orientation = frame::Orientation::Rotate0;
config.stream_orientation = None;
}
gst::EventView::Tag(ev) => {
let mut config = self.config.lock().unwrap();
let tags = ev.tag();
let scope = tags.scope();
let orientation = frame::Orientation::from_tags(tags);
if scope == gst::TagScope::Global {
config.global_orientation = orientation.unwrap_or(frame::Orientation::Rotate0);
} else {
config.stream_orientation = orientation;
}
}
_ => (),
}
self.parent_event(event)
}
} }
impl VideoSinkImpl for PaintableSink { impl VideoSinkImpl for PaintableSink {
@ -542,11 +585,14 @@ impl VideoSinkImpl for PaintableSink {
return Ok(gst::FlowSuccess::Ok); return Ok(gst::FlowSuccess::Ok);
}; };
let info = self.info.lock().unwrap(); let config = self.config.lock().unwrap();
let info = info.as_ref().ok_or_else(|| { let info = config.info.as_ref().ok_or_else(|| {
gst::error!(CAT, imp: self, "Received no caps yet"); gst::error!(CAT, imp: self, "Received no caps yet");
gst::FlowError::NotNegotiated gst::FlowError::NotNegotiated
})?; })?;
let orientation = config
.stream_orientation
.unwrap_or(config.global_orientation);
let wrapped_context = { let wrapped_context = {
#[cfg(not(any(target_os = "macos", target_os = "windows", feature = "gst-gl")))] #[cfg(not(any(target_os = "macos", target_os = "windows", feature = "gst-gl")))]
@ -566,7 +612,8 @@ impl VideoSinkImpl for PaintableSink {
} }
} }
}; };
let frame = Frame::new(buffer, info, wrapped_context.as_ref()).map_err(|err| { let frame =
Frame::new(buffer, info, orientation, wrapped_context.as_ref()).map_err(|err| {
gst::error!(CAT, imp: self, "Failed to map video frame"); gst::error!(CAT, imp: self, "Failed to map video frame");
err err
})?; })?;

View file

@ -38,7 +38,7 @@
use gtk::glib; use gtk::glib;
use gtk::glib::prelude::*; use gtk::glib::prelude::*;
mod frame; pub(super) mod frame;
pub(super) mod imp; pub(super) mod imp;
pub(super) mod paintable; pub(super) mod paintable;

View file

@ -13,7 +13,7 @@ use gtk::prelude::*;
use gtk::subclass::prelude::*; use gtk::subclass::prelude::*;
use gtk::{gdk, glib, graphene, gsk}; use gtk::{gdk, glib, graphene, gsk};
use crate::sink::frame::{Frame, Texture}; use crate::sink::frame::{self, Frame, Texture};
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
use std::collections::HashMap; use std::collections::HashMap;
@ -38,6 +38,7 @@ pub struct Paintable {
scaling_filter: Cell<gsk::ScalingFilter>, scaling_filter: Cell<gsk::ScalingFilter>,
use_scaling_filter: Cell<bool>, use_scaling_filter: Cell<bool>,
force_aspect_ratio: Cell<bool>, force_aspect_ratio: Cell<bool>,
orientation: Cell<frame::Orientation>,
#[cfg(not(feature = "gtk_v4_10"))] #[cfg(not(feature = "gtk_v4_10"))]
premult_shader: gsk::GLShader, premult_shader: gsk::GLShader,
} }
@ -53,6 +54,7 @@ impl Default for Paintable {
scaling_filter: Cell::new(gsk::ScalingFilter::Linear), scaling_filter: Cell::new(gsk::ScalingFilter::Linear),
use_scaling_filter: Cell::new(false), use_scaling_filter: Cell::new(false),
force_aspect_ratio: Cell::new(false), force_aspect_ratio: Cell::new(false),
orientation: Cell::new(frame::Orientation::Auto),
#[cfg(not(feature = "gtk_v4_10"))] #[cfg(not(feature = "gtk_v4_10"))]
premult_shader: gsk::GLShader::from_bytes(&glib::Bytes::from_static(include_bytes!( premult_shader: gsk::GLShader::from_bytes(&glib::Bytes::from_static(include_bytes!(
"premult.glsl" "premult.glsl"
@ -101,6 +103,10 @@ impl ObjectImpl for Paintable {
.blurb("When enabled, scaling will respect original aspect ratio") .blurb("When enabled, scaling will respect original aspect ratio")
.default_value(false) .default_value(false)
.build(), .build(),
glib::ParamSpecEnum::builder::<frame::Orientation>("orientation")
.nick("Orientation")
.blurb("Orientation of the video frames")
.build(),
] ]
}); });
@ -125,6 +131,7 @@ impl ObjectImpl for Paintable {
#[cfg(feature = "gtk_v4_10")] #[cfg(feature = "gtk_v4_10")]
"use-scaling-filter" => self.use_scaling_filter.get().to_value(), "use-scaling-filter" => self.use_scaling_filter.get().to_value(),
"force-aspect-ratio" => self.force_aspect_ratio.get().to_value(), "force-aspect-ratio" => self.force_aspect_ratio.get().to_value(),
"orientation" => self.orientation.get().to_value(),
_ => unimplemented!(), _ => unimplemented!(),
} }
} }
@ -148,6 +155,7 @@ impl ObjectImpl for Paintable {
#[cfg(feature = "gtk_v4_10")] #[cfg(feature = "gtk_v4_10")]
"use-scaling-filter" => self.use_scaling_filter.set(value.get().unwrap()), "use-scaling-filter" => self.use_scaling_filter.set(value.get().unwrap()),
"force-aspect-ratio" => self.force_aspect_ratio.set(value.get().unwrap()), "force-aspect-ratio" => self.force_aspect_ratio.set(value.get().unwrap()),
"orientation" => self.orientation.set(value.get().unwrap()),
_ => unimplemented!(), _ => unimplemented!(),
} }
} }
@ -156,7 +164,14 @@ impl ObjectImpl for Paintable {
impl PaintableImpl for Paintable { impl PaintableImpl for Paintable {
fn intrinsic_height(&self) -> i32 { fn intrinsic_height(&self) -> i32 {
if let Some(paintable) = self.paintables.borrow().first() { if let Some(paintable) = self.paintables.borrow().first() {
if self
.effective_orientation(paintable.orientation)
.is_flip_width_height()
{
f32::round(paintable.width) as i32
} else {
f32::round(paintable.height) as i32 f32::round(paintable.height) as i32
}
} else { } else {
0 0
} }
@ -164,7 +179,14 @@ impl PaintableImpl for Paintable {
fn intrinsic_width(&self) -> i32 { fn intrinsic_width(&self) -> i32 {
if let Some(paintable) = self.paintables.borrow().first() { if let Some(paintable) = self.paintables.borrow().first() {
if self
.effective_orientation(paintable.orientation)
.is_flip_width_height()
{
f32::round(paintable.height) as i32
} else {
f32::round(paintable.width) as i32 f32::round(paintable.width) as i32
}
} else { } else {
0 0
} }
@ -172,7 +194,14 @@ impl PaintableImpl for Paintable {
fn intrinsic_aspect_ratio(&self) -> f64 { fn intrinsic_aspect_ratio(&self) -> f64 {
if let Some(paintable) = self.paintables.borrow().first() { if let Some(paintable) = self.paintables.borrow().first() {
if self
.effective_orientation(paintable.orientation)
.is_flip_width_height()
{
paintable.height as f64 / paintable.width as f64
} else {
paintable.width as f64 / paintable.height as f64 paintable.width as f64 / paintable.height as f64
}
} else { } else {
0.0 0.0
} }
@ -180,7 +209,6 @@ impl PaintableImpl for Paintable {
fn snapshot(&self, snapshot: &gdk::Snapshot, width: f64, height: f64) { fn snapshot(&self, snapshot: &gdk::Snapshot, width: f64, height: f64) {
let snapshot = snapshot.downcast_ref::<gtk::Snapshot>().unwrap(); let snapshot = snapshot.downcast_ref::<gtk::Snapshot>().unwrap();
let background_color = self.background_color.get(); let background_color = self.background_color.get();
let force_aspect_ratio = self.force_aspect_ratio.get(); let force_aspect_ratio = self.force_aspect_ratio.get();
let paintables = self.paintables.borrow(); let paintables = self.paintables.borrow();
@ -201,8 +229,72 @@ impl PaintableImpl for Paintable {
// //
// Based on its size relative to the snapshot width/height, all other paintables are // Based on its size relative to the snapshot width/height, all other paintables are
// scaled accordingly. // scaled accordingly.
let (frame_width, frame_height) = (first_paintable.width, first_paintable.height); //
// We also only consider the orientation of the first paintable for now and rotate all
// overlays consistently with that to follow the behaviour of glvideoflip.
let effective_orientation = self.effective_orientation(first_paintable.orientation);
// First do the rotation around the center of the whole snapshot area
if effective_orientation != frame::Orientation::Rotate0 {
snapshot.translate(&graphene::Point::new(
width as f32 / 2.0,
height as f32 / 2.0,
));
}
match effective_orientation {
frame::Orientation::Rotate0 => {}
frame::Orientation::Rotate90 => {
snapshot.rotate(90.0);
}
frame::Orientation::Rotate180 => {
snapshot.rotate(180.0);
}
frame::Orientation::Rotate270 => {
snapshot.rotate(270.0);
}
frame::Orientation::FlipRotate0 => {
snapshot.rotate_3d(180.0, &gtk::graphene::Vec3::y_axis());
}
frame::Orientation::FlipRotate90 => {
snapshot.rotate(90.0);
snapshot.rotate_3d(180.0, &gtk::graphene::Vec3::y_axis());
}
frame::Orientation::FlipRotate180 => {
snapshot.rotate(180.0);
snapshot.rotate_3d(180.0, &gtk::graphene::Vec3::y_axis());
}
frame::Orientation::FlipRotate270 => {
snapshot.rotate(270.0);
snapshot.rotate_3d(180.0, &gtk::graphene::Vec3::y_axis());
}
frame::Orientation::Auto => unreachable!(),
}
if effective_orientation != frame::Orientation::Rotate0 {
if effective_orientation.is_flip_width_height() {
snapshot.translate(&graphene::Point::new(
-height as f32 / 2.0,
-width as f32 / 2.0,
));
} else {
snapshot.translate(&graphene::Point::new(
-width as f32 / 2.0,
-height as f32 / 2.0,
));
}
}
// The rotation is applied now and we're back at the origin at this point
// Width / height of the overall frame that we're drawing. This has to be flipped
// if a 90/270 degree rotation is applied.
let (frame_width, frame_height) = if effective_orientation.is_flip_width_height() {
(first_paintable.height, first_paintable.width)
} else {
(first_paintable.width, first_paintable.height)
};
// Amount of scaling that has to be applied to the main frame and all overlays to fill the
// available area
let mut scale_x = width / frame_width as f64; let mut scale_x = width / frame_width as f64;
let mut scale_y = height / frame_height as f64; let mut scale_y = height / frame_height as f64;
@ -227,14 +319,26 @@ impl PaintableImpl for Paintable {
} }
if !background_color.is_clear() && (trans_x > f64::EPSILON || trans_y > f64::EPSILON) { if !background_color.is_clear() && (trans_x > f64::EPSILON || trans_y > f64::EPSILON) {
// Clamping for the bounds below has to be flipped over for 90/270 degree rotations.
let (width, height) = if effective_orientation.is_flip_width_height() {
(height, width)
} else {
(width, height)
};
snapshot.append_color( snapshot.append_color(
&background_color, &background_color,
&graphene::Rect::new(0f32, 0f32, width as f32, height as f32), &graphene::Rect::new(0f32, 0f32, width as f32, height as f32),
); );
} }
if effective_orientation.is_flip_width_height() {
std::mem::swap(&mut trans_x, &mut trans_y);
}
snapshot.translate(&graphene::Point::new(trans_x as f32, trans_y as f32)); snapshot.translate(&graphene::Point::new(trans_x as f32, trans_y as f32));
} }
// At this point we're at the origin of the area into which the actual video frame is drawn
// Make immutable // Make immutable
let scale_x = scale_x; let scale_x = scale_x;
let scale_y = scale_y; let scale_y = scale_y;
@ -249,11 +353,19 @@ impl PaintableImpl for Paintable {
height: paintable_height, height: paintable_height,
global_alpha, global_alpha,
has_alpha, has_alpha,
orientation: _orientation,
}, },
) in paintables.iter().enumerate() ) in paintables.iter().enumerate()
{ {
snapshot.push_opacity(*global_alpha as f64); snapshot.push_opacity(*global_alpha as f64);
// Clamping for the bounds below has to be flipped over for 90/270 degree rotations.
let (width, height) = if effective_orientation.is_flip_width_height() {
(height, width)
} else {
(width, height)
};
let bounds = if !force_aspect_ratio && idx == 0 { let bounds = if !force_aspect_ratio && idx == 0 {
// While this should end up with width again, be explicit in this case to avoid // While this should end up with width again, be explicit in this case to avoid
// rounding errors and fill the whole area with the video frame. // rounding errors and fill the whole area with the video frame.
@ -261,11 +373,32 @@ impl PaintableImpl for Paintable {
} else { } else {
// Scale texture position and size with the same scale factor as the main video // Scale texture position and size with the same scale factor as the main video
// frame, and make sure to not render outside (0, 0, width, height). // frame, and make sure to not render outside (0, 0, width, height).
let x = f32::clamp(*x * scale_x as f32, 0.0, width as f32); let (rect_x, rect_y) = if effective_orientation.is_flip_width_height() {
let y = f32::clamp(*y * scale_y as f32, 0.0, height as f32); (
let texture_width = f32::min(*paintable_width * scale_x as f32, width as f32); f32::clamp(*y * scale_y as f32, 0.0, width as f32),
let texture_height = f32::min(*paintable_height * scale_y as f32, height as f32); f32::clamp(*x * scale_x as f32, 0.0, height as f32),
graphene::Rect::new(x, y, texture_width, texture_height) )
} else {
(
f32::clamp(*x * scale_x as f32, 0.0, width as f32),
f32::clamp(*y * scale_y as f32, 0.0, height as f32),
)
};
let (texture_width, texture_height) =
if effective_orientation.is_flip_width_height() {
(
f32::min(*paintable_width * scale_y as f32, width as f32),
f32::min(*paintable_height * scale_x as f32, height as f32),
)
} else {
(
f32::min(*paintable_width * scale_x as f32, width as f32),
f32::min(*paintable_height * scale_y as f32, height as f32),
)
};
graphene::Rect::new(rect_x, rect_y, texture_width, texture_height)
}; };
// Only premultiply GL textures that expect to be in premultiplied RGBA format. // Only premultiply GL textures that expect to be in premultiplied RGBA format.
@ -364,6 +497,19 @@ impl PaintableImpl for Paintable {
} }
impl Paintable { impl Paintable {
fn effective_orientation(
&self,
paintable_orientation: frame::Orientation,
) -> frame::Orientation {
let orientation = self.orientation.get();
if orientation != frame::Orientation::Auto {
return orientation;
}
assert_ne!(paintable_orientation, frame::Orientation::Auto);
paintable_orientation
}
pub(super) fn handle_frame_changed(&self, sink: &crate::PaintableSink, frame: Frame) { pub(super) fn handle_frame_changed(&self, sink: &crate::PaintableSink, frame: Frame) {
let context = self.gl_context.borrow(); let context = self.gl_context.borrow();