Draw pen hover position into image

This is useful to point out parts of your drawing when streaming to a
video conference.
This commit is contained in:
Lukas Werling 2021-01-29 22:53:56 +01:00
parent 57dcd7ca27
commit 66e98b1fee
4 changed files with 1106 additions and 28 deletions

970
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -8,3 +8,4 @@ edition = "2018"
anyhow = "1.0"
lz-fear = "0.1"
clap = "3.0.0-beta.2"
libremarkable = "0.4.3"

View File

@ -3,6 +3,7 @@
# default values for arguments
remarkable="10.11.99.1" # remarkable connected through USB
landscape=true # rotate 90 degrees to the right
cursor=false # show a cursor where the pen is hovering
output_path=- # display output through ffplay
format=- # automatic output format
webcam=false # not to a webcam
@ -18,6 +19,10 @@ while [ $# -gt 0 ]; do
landscape=false
shift
;;
-c | --cursor)
cursor=true
shift
;;
-s | --source)
remarkable="$2"
shift
@ -68,10 +73,11 @@ while [ $# -gt 0 ]; do
shift
;;
-h | --help | *)
echo "Usage: $0 [-p] [-u] [-s <source>] [-o <output>] [-f <format>] [-t <title>]"
echo "Usage: $0 [-p] [-c] [-u] [-s <source>] [-o <output>] [-f <format>] [-t <title>]"
echo "Examples:"
echo " $0 # live view in landscape"
echo " $0 -p # live view in portrait"
echo " $0 -c # live view with cursor"
echo " $0 -s 192.168.0.10 # connect to different IP"
echo " $0 -o remarkable.mp4 # record to a file"
echo " $0 -o udp://dest:1234 -f mpegts # record to a stream"
@ -199,6 +205,10 @@ set -e # stop if an error occurs
restream_options="-h $height -w $width -b $bytes_per_pixel -f $fb_file"
if "$cursor"; then
restream_options="$restream_options -c"
fi
# shellcheck disable=SC2089
restream_rs="PATH=\"\$PATH:/opt/bin/:.\" restream $restream_options"
if $unsecure_connection; then

View File

@ -6,6 +6,9 @@ use anyhow::{Context, Result};
use clap::{crate_authors, crate_version, Clap};
use lz_fear::CompressionSettings;
use libremarkable::cgmath;
use libremarkable::input::{ev::EvDevContext, wacom::WacomEvent, wacom::WacomPen, InputDevice, InputEvent};
use std::default::Default;
use std::fs::File;
use std::io::{BufRead, BufReader, Read, Seek, SeekFrom, Write};
@ -13,7 +16,7 @@ use std::net::{TcpStream, TcpListener};
use std::process::Command;
use std::time::Duration;
use std::thread;
use std::sync::mpsc::channel;
use std::sync::mpsc::{channel, Receiver};
#[derive(Clap)]
#[clap(version = crate_version!(), author = crate_authors!())]
@ -58,6 +61,14 @@ pub struct Opts {
)]
file: String,
#[clap(
long,
name = "cursor",
short = 'c',
about = "Show a cursor where the pen is hovering."
)]
show_cursor: bool,
}
fn main() -> Result<()> {
@ -72,7 +83,7 @@ fn main() -> Result<()> {
(opts.file.to_owned(), 0)
};
let streamer = ReStreamer::init(&file, offset, opts.width, opts.height, opts.bytes_per_pixel)?;
let streamer = ReStreamer::init(&file, offset, opts.width, opts.height, opts.bytes_per_pixel, opts.show_cursor)?;
let stdout = std::io::stdout();
let data_target: Box<dyn Write> = if let Some(port) = opts.listen {
@ -145,6 +156,14 @@ pub struct ReStreamer {
start: u64,
cursor: usize,
size: usize,
width: usize,
height: usize,
bytes_per_pixel: usize,
show_cursor: bool,
input_rx: Receiver<InputEvent>,
pen_pos: Option<(usize, usize)>,
drawing: bool,
}
impl ReStreamer {
@ -154,16 +173,30 @@ impl ReStreamer {
width: usize,
height: usize,
bytes_per_pixel: usize,
show_cursor: bool,
) -> Result<ReStreamer> {
let start = offset as u64;
let size = width * height * bytes_per_pixel;
let cursor = 0;
let file = File::open(path)?;
let (input_tx, input_rx) = channel::<InputEvent>();
if show_cursor {
EvDevContext::new(InputDevice::Wacom, input_tx).start();
}
let mut streamer = ReStreamer {
file,
start: start,
cursor,
size,
width,
height,
bytes_per_pixel,
show_cursor,
input_rx,
pen_pos: None,
drawing: false,
};
streamer.next_frame()?;
Ok(streamer)
@ -172,10 +205,119 @@ impl ReStreamer {
pub fn next_frame(&mut self) -> std::io::Result<()> {
self.file.seek(SeekFrom::Start(self.start))?;
self.cursor = 0;
if self.show_cursor {
self.read_input();
}
Ok(())
}
/// Read input events to figure out pen state and position.
fn read_input(&mut self) {
let mut down = self.pen_pos.is_some();
let mut pos = self.pen_pos.unwrap_or((0, 0));
while let Ok(event) = self.input_rx.try_recv() {
match event {
InputEvent::WacomEvent { event: e } =>
match e {
WacomEvent::InstrumentChange { pen: WacomPen::ToolPen, state: s } => {
down = s;
},
WacomEvent::Hover { position: cgmath::Point2 { x, y }, .. } => {
pos = (x as usize, y as usize);
self.drawing = false;
},
WacomEvent::Draw { .. } => {
// no need to show position while drawing
self.drawing = true;
},
_ => (),
},
_ => (),
}
}
self.pen_pos = if down {
Some(pos)
} else {
None
};
}
/// Draw pen position into fb data, if necessary (in hover range and not drawing).
fn draw_pen_position(&mut self, buf: &mut [u8]) {
if let (false, Some((y, x))) = (self.drawing, self.pen_pos) {
// we need negative numbers to calculate offsets correctly
let width = self.width as isize;
let height = self.height as isize;
let bpp = self.bytes_per_pixel as isize;
let cursor = self.cursor as isize;
for (i, (yoff, no)) in PEN_IMAGE.iter().enumerate() {
// we draw vertically (lines along y)
let xoff = i as isize - (PEN_IMAGE.len() as isize / 2);
let xstart = x as isize + xoff;
// line outside of canvas?
if xstart < 0 || xstart >= width {
continue
}
let mut ystart = (height - y as isize) + yoff;
let mut no = *no;
// cut-off at sides
if ystart < 0 {
no += ystart;
ystart = 0;
}
if ystart + no > height {
no = height - ystart;
}
if no <= 0 {
continue
}
// translate to buf indexes, check bounds and draw
let mut px_start = (xstart * height + ystart) * bpp;
let mut px_end = px_start + no * bpp;
// outside current buf?
if px_end < cursor || px_start >= cursor + buf.len() as isize {
continue
}
// truncate if partially outside
if px_start < cursor {
px_start = cursor;
}
if px_end > cursor + buf.len() as isize {
px_end = cursor + buf.len() as isize;
}
// invert pixel (on RM2)
// TODO: Do something sensible on RM1
for b in buf[(px_start-cursor) as usize..(px_end-cursor) as usize].iter_mut() {
*b = 255 - *b;
}
}
}
}
}
// Image of pen, given as (offset from pen position, number of pixels)
static PEN_IMAGE: [(isize, isize); 17] = [
(0, 1), // 00000000100000000
(0, 1), // 00000000100000000
(0, 1), // 00000000100000000
(-1, 3), // 00000001110000000
(-3, 7), // 00000111111100000
(-4, 9), // 00001111111110000
(-4, 9), // 00001111111110000
(-5, 11), // 00011111111111000
(-8, 17), // 11111111111111111
(-5, 11), // 00011111111111000
(-4, 9), // 00001111111110000
(-4, 9), // 00001111111110000
(-3, 7), // 00000111111100000
(-1, 3), // 00000001110000000
(0, 1), // 00000000100000000
(0, 1), // 00000000100000000
(0, 1), // 00000000100000000
];
impl Read for ReStreamer {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let requested = buf.len();
@ -185,6 +327,11 @@ impl Read for ReStreamer {
let rest = self.size - self.cursor;
self.file.read(&mut buf[0..rest])?
};
if self.show_cursor {
self.draw_pen_position(&mut buf[0..bytes_read]);
}
self.cursor += bytes_read;
if self.cursor == self.size {
self.next_frame()?;