Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Image size, for testing on main #12

Merged
merged 36 commits into from
Apr 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
5ca5ef1
resize images
Doublonmousse Feb 6, 2024
a68c55a
fix svg and use the transforms instead
Doublonmousse Feb 6, 2024
733d494
fmt
Doublonmousse Feb 6, 2024
d5c04a6
`is_fixed_layout` as a `Layout` method
Doublonmousse Feb 6, 2024
57a4971
remove unused `Layout` crate
Doublonmousse Feb 6, 2024
90db401
remove `resize` and uses an enum `ImageSizeOption` instead
Doublonmousse Feb 7, 2024
46bbc24
remove tracing debug and more docs
Doublonmousse Feb 7, 2024
ebb7a25
fmt
Doublonmousse Feb 7, 2024
0eb7a6e
Merge branch 'main' into image-size
Doublonmousse Feb 17, 2024
4f3f8f5
fmt
Doublonmousse Feb 17, 2024
58d2cce
refactor the resize code and add a special paste method to respect bo…
Doublonmousse Feb 18, 2024
17de25b
shift + drag and drop wip
Doublonmousse Feb 18, 2024
228b9fe
modularize the `resize` file
Doublonmousse Feb 19, 2024
7d8c3e4
use mutable cells to hold information about dnd status and modifiers
Doublonmousse Feb 19, 2024
e5d8c70
re add import though to be superflous
Doublonmousse Feb 19, 2024
776e7b8
remove debug traces and messages
Doublonmousse Feb 19, 2024
6009dd9
test stylus input
Doublonmousse Feb 20, 2024
de85331
test : resizing stroke content (wip)
Doublonmousse Feb 20, 2024
7ac5392
test : resizing stroke content (wip)
Doublonmousse Feb 20, 2024
73c7b96
Delete IMSIZE.md
Doublonmousse Feb 20, 2024
8ec0fe8
special paste for strokes
Doublonmousse Feb 20, 2024
a35a183
Merge branch 'image-size' of https://github.com/Doublonmousse/rnote i…
Doublonmousse Feb 20, 2024
f3a75b8
Merge branch 'main' into image-size
Doublonmousse Feb 20, 2024
f8a93d6
merge corrections (wip)
Doublonmousse Feb 20, 2024
2797cda
remove duplications
Doublonmousse Feb 20, 2024
f290a83
revert pen event for special dnd/paste mode
Doublonmousse Feb 20, 2024
70db4a2
force pasted content to be inbounds for fixed layouts
Doublonmousse Feb 20, 2024
7bc854e
clippy fixes
Doublonmousse Feb 24, 2024
793058d
Merge branch 'main' into image-size
Doublonmousse Mar 25, 2024
6de68e2
cleaning
Doublonmousse Mar 25, 2024
c3eca5e
more cleaning
Doublonmousse Mar 29, 2024
4b482bf
renaming
Doublonmousse Apr 20, 2024
0ae41c0
add a `respect border` toggle
Doublonmousse Apr 22, 2024
9ba9dc2
only use the `respect-borders` value from the windows
Doublonmousse Apr 22, 2024
0c1b4d7
tweaking `respect_borders` conditions (wip)
Doublonmousse Apr 22, 2024
d0efb2d
Merge branch 'image-size-testing' into image-size
Doublonmousse Apr 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions crates/rnote-engine/src/document/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ impl std::string::ToString for Layout {
}
}

impl Layout {
/// checks if the layout is constrained in the horizontal direction
pub fn is_fixed_width(&self) -> bool {
matches!(self, Layout::FixedSize | Layout::ContinuousVertical)
}
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default, rename = "document")]
pub struct Document {
Expand Down
58 changes: 56 additions & 2 deletions crates/rnote-engine/src/engine/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::pens::Pen;
use crate::pens::PenStyle;
use crate::store::chrono_comp::StrokeLayer;
use crate::store::StrokeKey;
use crate::strokes::{resize::calculate_resize_ratio, resize::ImageSizeOption, Resize};
use crate::strokes::{BitmapImage, Stroke, VectorImage};
use crate::{CloneConfig, Engine, WidgetFlags};
use futures::channel::oneshot;
Expand Down Expand Up @@ -234,14 +235,27 @@ impl Engine {
&self,
pos: na::Vector2<f64>,
bytes: Vec<u8>,
respect_borders: bool,
) -> oneshot::Receiver<anyhow::Result<VectorImage>> {
let (oneshot_sender, oneshot_receiver) = oneshot::channel::<anyhow::Result<VectorImage>>();

let resize_struct = Resize {
width: self.document.format.width(),
height: self.document.format.height(),
layout_fixed_width: self.document.layout.is_fixed_width(),
max_viewpoint: Some(self.camera.viewport().maxs),
restrain_to_viewport: true,
respect_borders,
};
rayon::spawn(move || {
let result = || -> anyhow::Result<VectorImage> {
let svg_str = String::from_utf8(bytes)?;

VectorImage::from_svg_str(&svg_str, pos, None)
VectorImage::from_svg_str(
&svg_str,
pos,
ImageSizeOption::ResizeImage(resize_struct),
)
};

if oneshot_sender.send(result()).is_err() {
Expand All @@ -261,12 +275,25 @@ impl Engine {
&self,
pos: na::Vector2<f64>,
bytes: Vec<u8>,
respect_borders: bool,
) -> oneshot::Receiver<anyhow::Result<BitmapImage>> {
let (oneshot_sender, oneshot_receiver) = oneshot::channel::<anyhow::Result<BitmapImage>>();

let resize_struct = Resize {
width: self.document.format.width(),
height: self.document.format.height(),
layout_fixed_width: self.document.layout.is_fixed_width(),
max_viewpoint: Some(self.camera.viewport().maxs),
restrain_to_viewport: true,
respect_borders,
};
rayon::spawn(move || {
let result = || -> anyhow::Result<BitmapImage> {
BitmapImage::from_image_bytes(&bytes, pos, None)
BitmapImage::from_image_bytes(
&bytes,
pos,
ImageSizeOption::ResizeImage(resize_struct),
)
};

if oneshot_sender.send(result()).is_err() {
Expand Down Expand Up @@ -428,6 +455,7 @@ impl Engine {
&mut self,
content: StrokeContent,
pos: na::Vector2<f64>,
resize: ImageSizeOption,
) -> WidgetFlags {
let mut widget_flags = WidgetFlags::default();

Expand All @@ -437,14 +465,40 @@ impl Engine {
self.store.set_selected_keys(&all_strokes, false);
widget_flags |= self.change_pen_style(PenStyle::Selector);

// calculate ratio
let ratio = match resize {
ImageSizeOption::ResizeImage(resize) => {
calculate_resize_ratio(resize, content.size().unwrap(), pos)
}
_ => 1.0f64,
};
let inserted_keys = self.store.insert_stroke_content(content, pos);

self.store.update_geometry_for_strokes(&inserted_keys);
self.store.regenerate_rendering_in_viewport_threaded(
self.tasks_tx.clone(),
false,
self.camera.viewport(),
self.camera.image_scale(),
);

self.store
.scale_strokes_with_pivot(&inserted_keys, na::Vector2::new(ratio, ratio), pos);
self.store.scale_strokes_images_with_pivot(
&inserted_keys,
na::Vector2::new(ratio, ratio),
pos,
);

// re generate view
self.store.update_geometry_for_strokes(&inserted_keys);
self.store.regenerate_rendering_in_viewport_threaded(
self.tasks_tx.clone(),
false,
self.camera.viewport(),
self.camera.image_scale(),
);

widget_flags |= self.penholder.current_pen_update_state(&mut EngineViewMut {
tasks_tx: self.tasks_tx.clone(),
pens_config: &mut self.pens_config,
Expand Down
27 changes: 21 additions & 6 deletions crates/rnote-engine/src/strokes/bitmapimage.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Imports
use super::resize::{calculate_resize_ratio, ImageSizeOption};
use super::{Content, Stroke};
use crate::document::Format;
use crate::engine::import::{PdfImportPageSpacing, PdfImportPrefs};
Expand Down Expand Up @@ -100,15 +101,27 @@ impl BitmapImage {
pub fn from_image_bytes(
bytes: &[u8],
pos: na::Vector2<f64>,
size: Option<na::Vector2<f64>>,
size_option: ImageSizeOption,
) -> Result<Self, anyhow::Error> {
let image = render::Image::try_from_encoded_bytes(bytes)?;
let size = size.unwrap_or_else(|| {
na::vector![f64::from(image.pixel_width), f64::from(image.pixel_height)]
});

let initial_size = na::vector![f64::from(image.pixel_width), f64::from(image.pixel_height)];

let (size, resize_ratio) = match size_option {
ImageSizeOption::RespectOriginalSize => (initial_size, 1.0f64),
ImageSizeOption::ImposeSize(given_size) => (given_size, 1.0f64),
ImageSizeOption::ResizeImage(resize_struct) => (
initial_size,
calculate_resize_ratio(resize_struct, initial_size, pos),
),
};

let mut transform = Transform::default();
transform.append_scale_mut(na::Vector2::new(resize_ratio, resize_ratio));
transform.append_translation_mut(pos + size * resize_ratio * 0.5);
let rectangle = Rectangle {
cuboid: p2d::shape::Cuboid::new(size * 0.5),
transform: Transform::new_w_isometry(na::Isometry2::new(pos + size * 0.5, 0.0)),
transform,
};
Ok(Self { image, rectangle })
}
Expand Down Expand Up @@ -215,7 +228,9 @@ impl BitmapImage {
.collect::<anyhow::Result<Vec<(Vec<u8>, na::Vector2<f64>, na::Vector2<f64>)>>>()?;

pngs.into_par_iter()
.map(|(png_data, pos, size)| Self::from_image_bytes(&png_data, pos, Some(size)))
.map(|(png_data, pos, size)| {
Self::from_image_bytes(&png_data, pos, ImageSizeOption::ImposeSize(size))
})
.collect()
}
}
2 changes: 2 additions & 0 deletions crates/rnote-engine/src/strokes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pub mod bitmapimage;
pub mod brushstroke;
pub mod content;
pub mod resize;
pub mod shapestroke;
pub mod stroke;
pub mod textstroke;
Expand All @@ -11,6 +12,7 @@ pub mod vectorimage;
pub use bitmapimage::BitmapImage;
pub use brushstroke::BrushStroke;
pub use content::Content;
pub use resize::Resize;
pub use shapestroke::ShapeStroke;
pub use stroke::Stroke;
pub use textstroke::TextStroke;
Expand Down
113 changes: 113 additions & 0 deletions crates/rnote-engine/src/strokes/resize.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/// Enum that lists the different options for sizing the image
///
/// Either respect the original image size (in pixel or dimensions)
/// for svg, impose a size, or resize based on the viewport/page
#[derive(Debug)]
pub enum ImageSizeOption {
/// respect the size of the original image (no resizing applied)
RespectOriginalSize,
/// Use the given size
ImposeSize(na::Vector2<f64>),
/// Resize the image with various constraints
ResizeImage(Resize),
}

#[derive(Debug)]
pub struct Resize {
/// width of a page
pub width: f64,
/// height of a page
pub height: f64,
/// if the layout has a fixed size vertically
pub layout_fixed_width: bool,
/// viewport
pub max_viewpoint: Option<na::OPoint<f64, na::Const<2>>>,
/// resize to the viewport
pub restrain_to_viewport: bool,
/// To force elements to not go over borders
/// maybe enabling that to be on only when borders are active
/// would be a better idea
pub respect_borders: bool,
}

/// helper functions for calculating resizing factors

/// Calculate where the next border of the page is
/// based on the current `position` and the `size` of
/// the page length
///
/// in conjunction with the the ratio min value, may
/// fail if the position is very close to a page border
fn helper_calculate_page_next_limit(position: &f64, size: &f64) -> f64 {
((position / size).floor() + 1.0f64) * size
}

/// Helper function to calculate ratios : min ratio for
/// the image to go from `current_position` to `current_size`
/// exactly
fn helper_calculate_fit_ratio(
max_position: &f64,
current_position: &f64,
current_size: &f64,
) -> f64 {
(max_position - current_position) / current_size
}

/// Calculate the `ratio` by which to resize the image such that
/// - it stays fully in view
/// - it does not goes over a page border when the mode has a fixed
/// width size
///
/// There is an additional constraint when the `respect_border`
/// bool of the `Resize` struct is true. In this case we disallow
/// images to go over to the next page on the right
///
/// `pos_left_top_canvas` is the position of the top-left corner of
/// the image in documents coordinates
pub fn calculate_resize_ratio(
resize: Resize,
initial_size_image: na::Vector2<f64>,
pos_left_top_canvas: na::Vector2<f64>,
) -> f64 {
let next_page_x = helper_calculate_page_next_limit(&pos_left_top_canvas.x, &resize.width);
let next_page_y = helper_calculate_page_next_limit(&pos_left_top_canvas.y, &resize.height);

// compile all ratio in a vec
let ratios = [
// check that we do not go out of the canvas view in the x direction
helper_calculate_fit_ratio(
&resize.max_viewpoint.unwrap_or(na::point![1.0, 1.0]).x,
&pos_left_top_canvas.x,
&initial_size_image.x,
),
// check that we do not go out of view in the y direction
helper_calculate_fit_ratio(
&resize.max_viewpoint.unwrap_or(na::point![1.0, 1.0]).y,
&pos_left_top_canvas.y,
&initial_size_image.y,
),
// check if we go out of the page on the right on fixed layout
helper_calculate_fit_ratio(&resize.width, &pos_left_top_canvas.x, &initial_size_image.x),
// check if we have to respect borders
helper_calculate_fit_ratio(&next_page_y, &pos_left_top_canvas.y, &initial_size_image.y), // vertical border (cut in the y direction)
helper_calculate_fit_ratio(&next_page_x, &pos_left_top_canvas.x, &initial_size_image.x), // horizontal border (cut in the x direction)
];

let is_provided_viewport = resize.max_viewpoint.is_some();

// apply rules
let apply_ratios = vec![
is_provided_viewport & resize.restrain_to_viewport, //canvas in the x direction
is_provided_viewport & resize.restrain_to_viewport, //canvas in the y direction
resize.layout_fixed_width, //do not go over the page on the right for fixed layout
resize.respect_borders, //do not go over the page on the bottom for all layouts
resize.respect_borders, //do not go over the page on the right for all layouts
];

ratios
.iter()
.zip(apply_ratios)
.filter(|x| x.1)
.fold(1.0f64, |acc, x| acc.min(*x.0))
.max(1e-15f64) //force the value to be positive as a zero would make transforms crash
}
42 changes: 29 additions & 13 deletions crates/rnote-engine/src/strokes/vectorimage.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Imports
use super::content::GeneratedContentImages;
use super::resize::{calculate_resize_ratio, ImageSizeOption};
use super::{Content, Stroke};
use crate::document::Format;
use crate::engine::import::{PdfImportPageSpacing, PdfImportPrefs};
Expand Down Expand Up @@ -137,7 +138,7 @@ impl VectorImage {
pub fn from_svg_str(
svg_data: &str,
pos: na::Vector2<f64>,
size: Option<na::Vector2<f64>>,
size_option: ImageSizeOption,
) -> Result<Self, anyhow::Error> {
const COORDINATES_PREC: u8 = 3;
const TRANSFORMS_PREC: u8 = 4;
Expand All @@ -159,18 +160,33 @@ impl VectorImage {
svg_tree.size().height() as f64
];
let svg_data = svg_tree.to_string(&xml_options);
let rectangle = if let Some(size) = size {
Rectangle {
cuboid: p2d::shape::Cuboid::new(size * 0.5),
transform: Transform::new_w_isometry(na::Isometry2::new(pos + size * 0.5, 0.0)),

let mut transform = Transform::default();
let rectangle = match size_option {
ImageSizeOption::RespectOriginalSize => {
// Size not given : use the intrisic size
transform.append_translation_mut(pos + intrinsic_size * 0.5);
Rectangle {
cuboid: p2d::shape::Cuboid::new(intrinsic_size * 0.5),
transform,
}
}
} else {
Rectangle {
cuboid: p2d::shape::Cuboid::new(intrinsic_size * 0.5),
transform: Transform::new_w_isometry(na::Isometry2::new(
pos + intrinsic_size * 0.5,
0.0,
)),
ImageSizeOption::ImposeSize(given_size) => {
// Size given : use the given size
transform.append_translation_mut(pos + given_size * 0.5);
Rectangle {
cuboid: p2d::shape::Cuboid::new(given_size * 0.5),
transform,
}
}
ImageSizeOption::ResizeImage(resize_struct) => {
// Resize : calculate the ratio
let resize_ratio = calculate_resize_ratio(resize_struct, intrinsic_size, pos);
transform.append_translation_mut(pos + intrinsic_size * resize_ratio * 0.5);
Rectangle {
cuboid: p2d::shape::Cuboid::new(intrinsic_size * resize_ratio * 0.5),
transform,
}
}
};

Expand Down Expand Up @@ -311,7 +327,7 @@ impl VectorImage {
Self::from_svg_str(
svg.svg_data.as_str(),
svg.bounds.mins.coords,
Some(svg.bounds.extents()),
ImageSizeOption::ImposeSize(svg.bounds.extents()),
)
})
.collect()
Expand Down
4 changes: 4 additions & 0 deletions crates/rnote-ui/data/app.gschema.xml.in
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@
<default>false</default>
<summary>block pinch to zoom</summary>
</key>
<key name="respect-borders" type="b">
<default>false</default>
<summary>respect borders when pasting</summary>
</key>
<key name="touch-drawing" type="b">
<default>false</default>
<summary>enable drawing with touch input</summary>
Expand Down
5 changes: 5 additions & 0 deletions crates/rnote-ui/data/ui/canvasmenu.ui
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@
<attribute name="toggle" />
<attribute name="action">win.block-pinch-zoom</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Respect Borders when Pasting</attribute>
<attribute name="toggle"/>
<attribute name="action">win.respect-borders</attribute>
</item>
</section>
</menu>

Expand Down
Loading
Loading