From 31195932920cda0d43b76675388cbae6e7072158 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 28 Aug 2018 11:06:01 +0300 Subject: [PATCH] Add pango-cairo example --- examples/Cargo.toml | 8 ++ examples/src/bin/pango-cairo.rs | 211 ++++++++++++++++++++++++++++++++ 2 files changed, 219 insertions(+) create mode 100644 examples/src/bin/pango-cairo.rs diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 8bd5adc7d..eb0f5cf3e 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -21,6 +21,9 @@ futures-preview = { version = "0.2", optional = true } byte-slice-cast = "0.2" failure = "0.1" failure_derive = "0.1" +cairo-rs = { git = "https://github.com/gtk-rs/cairo", features=["use_glib"], optional = true } +pango = { git = "https://github.com/gtk-rs/pango", optional = true } +pangocairo = { git = "https://github.com/gtk-rs/pangocairo", optional = true } [features] gst-player = ["gstreamer-player"] @@ -34,6 +37,7 @@ gst-rtsp-server = ["gstreamer-rtsp-server"] gst-rtsp-server-record = ["gstreamer-rtsp-server", "gstreamer-rtsp", "gio"] default-features = [] v1_10 = ["gstreamer/v1_10"] +pango-cairo = ["pango", "pangocairo", "cairo-rs"] [badges] travis-ci = { repository = "sdroege/gstreamer-rs", branch = "master" } @@ -110,3 +114,7 @@ required-features = ["gst-rtsp-server-record"] [[bin]] name = "discoverer" + +[[bin]] +name = "pango-cairo" +required-features = ["pango-cairo"] diff --git a/examples/src/bin/pango-cairo.rs b/examples/src/bin/pango-cairo.rs new file mode 100644 index 000000000..d0a3bbe5d --- /dev/null +++ b/examples/src/bin/pango-cairo.rs @@ -0,0 +1,211 @@ +extern crate glib; + +extern crate gstreamer as gst; +use gst::prelude::*; + +extern crate cairo; +extern crate gstreamer_video as gst_video; +extern crate pango; +use pango::prelude::*; +extern crate pangocairo; + +use std::error::Error as StdError; +use std::ops; +use std::sync::{Arc, Mutex}; + +extern crate failure; +use failure::Error; + +#[macro_use] +extern crate failure_derive; + +#[path = "../examples-common.rs"] +mod examples_common; + +#[derive(Debug, Fail)] +#[fail(display = "Missing element {}", _0)] +struct MissingElement(&'static str); + +#[derive(Debug, Fail)] +#[fail( + display = "Received error from {}: {} (debug: {:?})", + src, + error, + debug +)] +struct ErrorMessage { + src: String, + error: String, + debug: Option, + #[cause] + cause: glib::Error, +} + +struct DrawingContext { + layout: glib::SendUniqueCell, + info: Option, +} + +#[derive(Debug)] +struct LayoutWrapper(pango::Layout); + +impl ops::Deref for LayoutWrapper { + type Target = pango::Layout; + + fn deref(&self) -> &pango::Layout { + &self.0 + } +} + +unsafe impl glib::SendUnique for LayoutWrapper { + fn is_unique(&self) -> bool { + self.0.ref_count() == 1 + } +} + +fn create_pipeline() -> Result { + gst::init()?; + + let pipeline = gst::Pipeline::new(None); + let src = + gst::ElementFactory::make("videotestsrc", None).ok_or(MissingElement("videotestsrc"))?; + let overlay = + gst::ElementFactory::make("cairooverlay", None).ok_or(MissingElement("cairooverlay"))?; + let capsfilter = + gst::ElementFactory::make("capsfilter", None).ok_or(MissingElement("capsfilter"))?; + let videoconvert = + gst::ElementFactory::make("videoconvert", None).ok_or(MissingElement("videoconvert"))?; + let sink = + gst::ElementFactory::make("autovideosink", None).ok_or(MissingElement("autovideosink"))?; + + pipeline.add_many(&[&src, &overlay, &capsfilter, &videoconvert, &sink])?; + gst::Element::link_many(&[&src, &overlay, &capsfilter, &videoconvert, &sink])?; + + let caps = gst::Caps::builder("video/x-raw") + .field("width", &800i32) + .field("height", &800i32) + .build(); + capsfilter.set_property("caps", &caps).unwrap(); + + src.set_property_from_str("pattern", "ball"); + + let fontmap = pangocairo::FontMap::new().unwrap(); + let context = fontmap.create_context().unwrap(); + let layout = LayoutWrapper(pango::Layout::new(&context)); + + let font_desc = pango::FontDescription::from_string("Sans Bold 26"); + layout.set_font_description(&font_desc); + layout.set_text("GStreamer"); + + let drawer = Arc::new(Mutex::new(DrawingContext { + layout: glib::SendUniqueCell::new(layout).unwrap(), + info: None, + })); + + let drawer_clone = drawer.clone(); + overlay + .connect("draw", false, move |args| { + use std::f64::consts::PI; + + let drawer = &drawer_clone; + let drawer = drawer.lock().unwrap(); + + let _overlay = args[0].get::().unwrap(); + let cr = args[1].get::().unwrap(); + let timestamp = args[2].get::().unwrap(); + let _duration = args[3].get::().unwrap(); + + let info = drawer.info.as_ref().unwrap(); + let layout = drawer.layout.borrow(); + + let angle = 2.0 + * PI + * ((timestamp % (10 * gst::SECOND)).unwrap() as f64 + / (10.0 * gst::SECOND_VAL as f64)); + + cr.translate(info.width() as f64 / 2.0, info.height() as f64 / 2.0); + cr.rotate(angle); + + for i in 0..10 { + cr.save(); + + let angle = (360. * i as f64) / 10.0; + let red = (1.0 + f64::cos((angle - 60.0) * PI / 180.0)) / 2.0; + cr.set_source_rgb(red, 0.0, 1.0 - red); + cr.rotate(angle * PI / 180.0); + + pangocairo::functions::update_layout(&cr, &layout); + let (width, _height) = layout.get_size(); + cr.move_to( + -(width as f64 / pango::SCALE as f64) / 2.0, + -(info.height() as f64) / 2.0, + ); + pangocairo::functions::show_layout(&cr, &layout); + + cr.restore(); + } + + None + }).unwrap(); + + let drawer_clone = drawer.clone(); + overlay + .connect("caps-changed", false, move |args| { + let _overlay = args[0].get::().unwrap(); + let caps = args[1].get::().unwrap(); + + let drawer = &drawer_clone; + let mut drawer = drawer.lock().unwrap(); + drawer.info = Some(gst_video::VideoInfo::from_caps(&caps).unwrap()); + + None + }).unwrap(); + + Ok(pipeline) +} + +fn main_loop(pipeline: gst::Pipeline) -> Result<(), Error> { + pipeline.set_state(gst::State::Playing).into_result()?; + + let bus = pipeline + .get_bus() + .expect("Pipeline without bus. Shouldn't happen!"); + + while let Some(msg) = bus.timed_pop(gst::CLOCK_TIME_NONE) { + use gst::MessageView; + + match msg.view() { + MessageView::Eos(..) => break, + MessageView::Error(err) => { + pipeline.set_state(gst::State::Null).into_result()?; + Err(ErrorMessage { + src: err + .get_src() + .map(|s| s.get_path_string()) + .unwrap_or_else(|| String::from("None")), + error: err.get_error().description().into(), + debug: err.get_debug(), + cause: err.get_error(), + })?; + } + _ => (), + } + } + + pipeline.set_state(gst::State::Null).into_result()?; + + Ok(()) +} + +fn example_main() { + match create_pipeline().and_then(main_loop) { + Ok(r) => r, + Err(e) => eprintln!("Error! {}", e), + } +} + +fn main() { + // tutorials_common::run is only required to set up the application environent on macOS + // (but not necessary in normal Cocoa applications where this is set up autmatically) + examples_common::run(example_main); +}