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

Preserve the aspect ratio of a clipped region in an Image #2195

Merged
merged 1 commit into from
Jul 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ You can find its changes [documented below](#070---2021-01-01).
- `ListIter` implementations for `Vector<T>` and `(S, Vector<T>)` ([#1967] by [@xarvic])
- Do not panic in Application::try_global if Application is not created ([#1996] by [@Maan2003])
- X11: window focus events ([#1938] by [@Maan2003]
- Preserve the aspect ratio of a clipped region in an Image ([#2195] by [@barsae])

### Visual

Expand Down Expand Up @@ -558,6 +559,7 @@ Last release without a changelog :(
[@twitchyliquid64]: https://github.com/twitchyliquid64
[@dristic]: https://github.com/dristic
[@NickLarsenNZ]: https://github.com/NickLarsenNZ
[@barsae]: https://github.com/barsae

[#599]: https://github.com/linebender/druid/pull/599
[#611]: https://github.com/linebender/druid/pull/611
Expand Down Expand Up @@ -851,6 +853,7 @@ Last release without a changelog :(
[#2157]: https://github.com/linebender/druid/pull/2157
[#2158]: https://github.com/linebender/druid/pull/2158
[#2172]: https://github.com/linebender/druid/pull/2172
[#2195]: https://github.com/linebender/druid/pull/2195
[#2196]: https://github.com/linebender/druid/pull/2196
[#2203]: https://github.com/linebender/druid/pull/2203

Expand Down
66 changes: 65 additions & 1 deletion druid/examples/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use druid::widget::{prelude::*, FillStrat, Image};
use druid::widget::{
Checkbox, CrossAxisAlignment, Flex, Label, RadioGroup, SizedBox, TextBox, WidgetExt,
};
use druid::{AppLauncher, Color, Data, ImageBuf, Lens, WindowDesc};
use druid::{AppLauncher, Color, Data, ImageBuf, Lens, Rect, WindowDesc};

static FILL_STRAT_OPTIONS: &[(&str, FillStrat)] = &[
("Contain", FillStrat::Contain),
Expand All @@ -50,6 +50,11 @@ struct AppState {
width: f64,
fix_height: bool,
height: f64,
clip: bool,
clip_x: f64,
clip_y: f64,
clip_width: f64,
clip_height: f64,
}

/// builds a child Flex widget from some paramaters.
Expand Down Expand Up @@ -144,6 +149,8 @@ fn make_control_row() -> impl Widget<AppState> {
.with_default_spacer()
.with_child(Checkbox::new("Fix height").lens(AppState::fix_height))
.with_default_spacer()
.with_child(Checkbox::new("Clip").lens(AppState::clip))
.with_default_spacer()
.with_child(Checkbox::new("set interpolation mode").lens(AppState::interpolate)),
)
.padding(10.0)
Expand All @@ -164,6 +171,28 @@ fn make_width() -> impl Widget<AppState> {
.fix_width(60.0),
),
)
.with_default_spacer()
.with_child(Label::new("clip x:"))
.with_default_spacer()
.with_child(
Flex::row().with_child(
TextBox::new()
.with_formatter(ParseFormatter::new())
.lens(AppState::clip_x)
.fix_width(60.0),
),
)
.with_default_spacer()
.with_child(Label::new("clip width:"))
.with_default_spacer()
.with_child(
Flex::row().with_child(
TextBox::new()
.with_formatter(ParseFormatter::new())
.lens(AppState::clip_width)
.fix_width(60.0),
),
)
}
fn make_height() -> impl Widget<AppState> {
Flex::column()
Expand All @@ -178,6 +207,28 @@ fn make_height() -> impl Widget<AppState> {
.fix_width(60.0),
),
)
.with_default_spacer()
.with_child(Label::new("clip y:"))
.with_default_spacer()
.with_child(
Flex::row().with_child(
TextBox::new()
.with_formatter(ParseFormatter::new())
.lens(AppState::clip_y)
.fix_width(60.0),
),
)
.with_default_spacer()
.with_child(Label::new("clip height:"))
.with_default_spacer()
.with_child(
Flex::row().with_child(
TextBox::new()
.with_formatter(ParseFormatter::new())
.lens(AppState::clip_height)
.fix_width(60.0),
),
)
}

fn build_widget(state: &AppState) -> Box<dyn Widget<AppState>> {
Expand All @@ -187,6 +238,14 @@ fn build_widget(state: &AppState) -> Box<dyn Widget<AppState>> {
if state.interpolate {
img.set_interpolation_mode(state.interpolation_mode)
}
if state.clip {
img.set_clip_area(Some(Rect::new(
state.clip_x,
state.clip_y,
state.clip_x + state.clip_width,
state.clip_y + state.clip_height,
)));
}
let mut sized = SizedBox::new(img);
if state.fix_width {
sized = sized.fix_width(state.width)
Expand Down Expand Up @@ -219,6 +278,11 @@ pub fn main() {
width: 200.,
fix_height: true,
height: 100.,
clip: false,
clip_x: 0.,
clip_y: 0.,
clip_width: 50.,
clip_height: 50.,
};

AppLauncher::with_window(main_window)
Expand Down
28 changes: 14 additions & 14 deletions druid/src/widget/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,14 @@ impl Image {
fn invalidate(&mut self) {
self.paint_data = None;
}

/// The size of the effective image, considering clipping if it's in effect.
#[inline]
fn image_size(&mut self) -> Size {
self.clip_area
.map(|a| a.size())
.unwrap_or_else(|| self.image_data.size())
}
}

impl<T: Data> Widget<T> for Image {
Expand Down Expand Up @@ -193,23 +201,24 @@ impl<T: Data> Widget<T> for Image {
// in the size exactly. If it is unconstrained by both width and height take the size of
// the image.
let max = bc.max();
let image_size = self.image_data.size();
let image_size = self.image_size();
let size = if bc.is_width_bounded() && !bc.is_height_bounded() {
let ratio = max.width / image_size.width;
Size::new(max.width, ratio * image_size.height)
} else if bc.is_height_bounded() && !bc.is_width_bounded() {
let ratio = max.height / image_size.height;
Size::new(ratio * image_size.width, max.height)
} else {
bc.constrain(self.image_data.size())
bc.constrain(image_size)
};
trace!("Computed size: {}", size);
size
}

#[instrument(name = "Image", level = "trace", skip(self, ctx, _data, _env))]
fn paint(&mut self, ctx: &mut PaintCtx, _data: &T, _env: &Env) {
let offset_matrix = self.fill.affine_to_fill(ctx.size(), self.image_data.size());
let image_size = self.image_size();
let offset_matrix = self.fill.affine_to_fill(ctx.size(), image_size);

// The ImageData's to_piet function does not clip to the image's size
// CairoRenderContext is very like druids but with some extra goodies like clip
Expand All @@ -236,18 +245,9 @@ impl<T: Data> Widget<T> for Image {
};
ctx.transform(offset_matrix);
if let Some(area) = self.clip_area {
ctx.draw_image_area(
piet_image,
area,
self.image_data.size().to_rect(),
self.interpolation,
);
ctx.draw_image_area(piet_image, area, image_size.to_rect(), self.interpolation);
} else {
ctx.draw_image(
piet_image,
self.image_data.size().to_rect(),
self.interpolation,
);
ctx.draw_image(piet_image, image_size.to_rect(), self.interpolation);
}
});
}
Expand Down