Skip to main content

kas_image/
lib.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License in the LICENSE-APACHE file or at:
4//     https://www.apache.org/licenses/LICENSE-2.0
5
6//! KAS resvg & tiny-skia integration
7//!
8//! This crate provides [`Svg`] and [`Canvas`] widgets using the [tiny-skia] and
9//! [resvg] libraries by [Yevhenii Reizner "RazrFalcon"](https://github.com/RazrFalcon/).
10//!
11//! [tiny-skia]: https://crates.io/crates/tiny-skia
12//! [resvg]: https://crates.io/crates/resvg
13
14#![cfg_attr(docsrs, feature(doc_cfg))]
15
16pub extern crate tiny_skia;
17
18#[cfg(feature = "canvas")] mod canvas;
19#[cfg(feature = "image")] mod image;
20mod sprite;
21#[cfg(feature = "svg")] mod svg;
22
23#[cfg(feature = "canvas")]
24pub use canvas::{Canvas, CanvasProgram};
25#[cfg(feature = "image")] pub use image::Image;
26pub use sprite::Sprite;
27#[cfg(feature = "svg")] pub use svg::Svg;
28
29use kas::cast::{Conv, ConvFloat};
30use kas::geom::{Rect, Vec2};
31use kas::impl_scope;
32use kas::layout::{AlignPair, AxisInfo, LogicalSize, SizeRules, Stretch};
33use kas::theme::{MarginStyle, SizeCx};
34
35/// Load a window icon from a path
36#[cfg(feature = "image")]
37pub fn window_icon_from_path<P: AsRef<std::path::Path>>(
38    path: P,
39) -> Result<kas::window::icon::Icon, Box<dyn std::error::Error>> {
40    // TODO(opt): image loading could be de-duplicated with
41    // DrawShared::image_from_path, but this may not be worthwhile.
42    let im = ::image::ImageReader::open(path)?
43        .with_guessed_format()?
44        .decode()?
45        .into_rgba8();
46    let (w, h) = im.dimensions();
47    let icon = kas::window::icon::RgbaIcon::new(im.into_vec(), w, h)?;
48    Ok(icon.into())
49}
50
51impl_scope! {
52    /// Control over image scaling
53    #[impl_default]
54    #[derive(Clone, Debug, PartialEq)]
55    struct Scaling {
56        /// Display size (logical pixels)
57        ///
58        /// This may be set by the providing type or by the user.
59        pub size: LogicalSize,
60        /// Margins
61        pub margins: MarginStyle,
62        /// If true, aspect ratio is fixed relative to [`Self::size`]
63        ///
64        /// Default: `true`
65        pub fix_aspect: bool = true,
66        /// Widget stretchiness
67        ///
68        /// If is `None`, max size is limited to ideal size.
69        ///
70        /// By default, this is `None`.
71        pub stretch: Stretch,
72    }
73}
74
75impl Scaling {
76    /// Generates [`SizeRules`] based on size
77    pub fn size_rules(&mut self, cx: &mut SizeCx, axis: AxisInfo) -> SizeRules {
78        let scale_factor = cx.scale_factor();
79        let ideal = self.size.to_physical(scale_factor).extract(axis);
80        SizeRules::new(ideal, ideal, self.stretch)
81            .with_margins(cx.margins(self.margins).extract(axis))
82    }
83
84    /// Constrains and aligns within the given `rect`
85    ///
86    /// This aligns content when using [`Stretch::None`] and when fixed-aspect
87    /// scaling constrains size.
88    pub fn align(&mut self, rect: Rect, align: AlignPair, scale_factor: f32) -> Rect {
89        let mut size = rect.size;
90
91        if self.stretch == Stretch::None {
92            let ideal = self.size.to_physical(scale_factor);
93            size = size.min(ideal);
94        }
95
96        if self.fix_aspect {
97            let logical_size = Vec2::from(self.size);
98            let Vec2(rw, rh) = Vec2::conv(size) / logical_size;
99
100            // Use smaller ratio, if any is finite
101            if rw < rh {
102                size.1 = i32::conv_nearest(rw * logical_size.1);
103            } else if rh < rw {
104                size.0 = i32::conv_nearest(rh * logical_size.0);
105            }
106        }
107
108        align.aligned_rect(size, rect)
109    }
110}