Rust图形渲染库iced_tiny_skia的使用,轻量级2D图形绘制与跨平台UI集成方案

Rust图形渲染库iced_tiny_skia的使用,轻量级2D图形绘制与跨平台UI集成方案

安装

在项目目录中运行以下Cargo命令:

cargo add iced_tiny_skia

或者在Cargo.toml中添加以下行:

iced_tiny_skia = "0.13.0"

基本使用示例

以下是一个完整的iced_tiny_skia使用示例,展示了如何创建一个简单的2D图形绘制应用:

use iced::{
    widget::{canvas, container},
    Application, Color, Command, Element, Length, Settings, Theme,
};
use iced_tiny_skia as tiny_skia;

struct Example {
    cache: canvas::Cache,
}

#[derive(Debug, Clone)]
enum Message {}

impl Application for Example {
    type Executor = iced::executor::Default;
    type Message = Message;
    type Theme = Theme;
    type Flags = ();

    fn new(_flags: ()) -> (Self, Command<Message>) {
        (
            Example {
                cache: canvas::Cache::new(),
            },
            Command::none(),
        )
    }

    fn title(&self) -> String {
        String::from("iced_tiny_skia示例")
    }

    fn update(&mut self, _message: Message) -> Command<Message> {
        Command::none()
    }

    fn view(&self) -> Element<Message> {
        let canvas = canvas(self as &Self)
            .width(Length::Fill)
            .height(Length::Fill);

        container(canvas)
            .width(Length::Fill)
            .height(Length::Fill)
            .padding(20)
            .into()
    }
}

impl canvas::Program<Message> for Example {
    type State = ();

    fn draw(
        &self,
        _state: &Self::State,
        renderer: &iced::Renderer,
        theme: &Theme,
        bounds: iced::Rectangle,
        _cursor: canvas::Cursor,
    ) -> Vec<canvas::Geometry> {
        let geometry = self.cache.draw(renderer, bounds.size(), |frame| {
            // 使用Tiny Skia绘制2D图形
            let mut pixmap = tiny_skia::Pixmap::new(
                frame.width() as u32,
                frame.height() as u32,
            )
            .unwrap();

            // 创建画笔
            let mut paint = tiny_skia::Paint::default();
            paint.set_color(tiny_skia::Color::from_rgba8(255, 0, 0, 255));
            paint.anti_alias = true;

            // 绘制一个圆
            let circle = tiny_skia::PathBuilder::from_circle(
                frame.width() / 2.0,
                frame.height() / 2.0,
                100.0,
            )
            .unwrap();

            pixmap.fill_path(
                &circle,
                &paint,
                tiny_skia::FillRule::Winding,
                tiny_skia::Transform::identity(),
                None,
            );

            // 将Tiny Skia的Pixmap转换为iced的图像
            frame.draw_image(canvas::Image::new(
                iced::advanced::graphics::image::Handle::from_pixels(
                    frame.width(),
                    frame.height(),
                    pixmap.data().to_vec(),
                ),
            ));
        });

        vec![geometry]
    }
}

fn main() -> iced::Result {
    Example::run(Settings {
        window: iced::window::Settings {
            size: (800, 600),
            ..Default::default()
        },
        ..Default::default()
    })
}

功能说明

  1. 轻量级2D渲染:iced_tiny_skia基于Tiny Skia库,提供高效的2D图形渲染能力
  2. 跨平台UI集成:可与iced UI框架无缝集成,创建跨平台的图形界面应用
  3. 简单易用:提供简洁的API用于绘制基本图形、路径和图像

完整示例代码

以下是一个更完整的示例,展示了如何使用iced_tiny_skia绘制多种图形:

use iced::{
    widget::{canvas, container, text},
    Application, Color, Command, Element, Length, Settings, Theme,
};
use iced_tiny_skia as tiny_skia;

struct DrawingApp {
    cache: canvas::Cache,
}

#[derive(Debug, Clone)]
enum Message {
    Redraw,
}

impl Application for DrawingApp {
    type Executor = iced::executor::Default;
    type Message = Message;
    type Theme = Theme;
    type Flags = ();

    fn new(_flags: ()) -> (Self, Command<Message>) {
        (
            DrawingApp {
                cache: canvas::Cache::new(),
            },
            Command::none(),
        )
    }

    fn title(&self) -> String {
        String::from("高级iced_tiny_skia示例")
    }

    fn update(&mut self, message: Message) -> Command<Message> {
        match message {
            Message::Redraw => {
                self.cache.clear();
                Command::none()
            }
        }
    }

    fn view(&self) -> Element<Message> {
        let content = container(
            iced::widget::column![
                text("点击画布重新绘制图形").size(20),
                canvas(self as &Self)
                    .width(Length::Fill)
                    .height(Length::Fill)
            ]
            .spacing(10),
        )
        .width(Length::Fill)
        .height(Length::Fill)
        .padding(20)
        .center_x()
        .center_y();

        content.into()
    }
}

impl canvas::Program<Message> for DrawingApp {
    type State = ();

    fn draw(
        &self,
        _state: &Self::State,
        renderer: &iced::Renderer,
        _theme: &Theme,
        bounds: iced::Rectangle,
        _cursor: canvas::Cursor,
    ) -> Vec<canvas::Geometry> {
        let geometry = self.cache.draw(renderer, bounds.size(), |frame| {
            // 创建Tiny Skia画布
            let mut pixmap = tiny_skia::Pixmap::new(
                frame.width() as u32,
                frame.height() as u32,
            )
            .unwrap();

            // 绘制渐变背景
            let gradient = tiny_skia::LinearGradient::new(
                tiny_skia::Point::from_xy(0.0, 0.0),
                tiny_skia::Point::from_xy(frame.width(), frame.height()),
                vec![
                    tiny_skia::GradientStop::new(0.0, tiny_skia::Color::from_rgba8(100, 150, 255, 255)),
                    tiny_skia::GradientStop::new(1.0, tiny_skia::Color::from_rgba8(200, 100, 255, 255)),
                ],
                tiny_skia::SpreadMode::Pad,
                tiny_skia::Transform::identity(),
            )
            .unwrap();

            let mut paint = tiny_skia::Paint::default();
            paint.shader = gradient;
            pixmap.fill_rect(
                tiny_skia::Rect::from_xywh(
                    0.0,
                    0.0,
                    frame.width(),
                    frame.height(),
                )
                .unwrap(),
                &paint,
                tiny_skia::Transform::identity(),
                None,
            );

            // 绘制红色圆形
            let mut circle_paint = tiny_skia::Paint::default();
            circle_paint.set_color(tiny_skia::Color::from_rgba8(255, 0, 0, 200));
            circle_paint.anti_alias = true;

            let circle = tiny_skia::PathBuilder::from_circle(
                frame.width() * 0.25,
                frame.height() / 2.0,
                80.0,
            )
            .unwrap();

            pixmap.fill_path(
                &circle,
                &circle_paint,
                tiny_skia::FillRule::Winding,
                tiny_skia::Transform::identity(),
                None,
            );

            // 绘制蓝色矩形
            let mut rect_paint = tiny_skia::Paint::default();
            rect_paint.set_color(tiny_skia::Color::from_rgba8(0, 0, 255, 180));
            
            let rect = tiny_skia::Rect::from_xywh(
                frame.width() * 0.5 - 100.0,
                frame.height() / 2.0 - 60.0,
                200.0,
                120.0,
            )
            .unwrap();

            pixmap.fill_rect(
                &rect,
                &rect_paint,
                tiny_skia::Transform::identity(),
                None,
            );

            // 绘制绿色三角形
            let mut triangle_paint = tiny_skia::Paint::default();
            triangle_paint.set_color(tiny_skia::Color::from_rgba8(0, 255, 0, 220));
            
            let mut pb = tiny_skia::PathBuilder::new();
            pb.move_to(frame.width() * 0.75, frame.height() / 2.0 - 80.0);
            pb.line_to(frame.width() * 0.75 + 80.0, frame.height() / 2.0 + 80.0);
            pb.line_to(frame.width() * 0.75 - 80.0, frame.height() / 2.0 + 80.0);
            pb.close();
            let triangle = pb.finish().unwrap();

            pixmap.fill_path(
                &triangle,
                &triangle_paint,
                tiny_skia::FillRule::Winding,
                tiny_skia::Transform::identity(),
                None,
            );

            // 转换为iced图像
            frame.draw_image(canvas::Image::new(
                iced::advanced::graphics::image::Handle::from_pixels(
                    frame.width(),
                    frame.height(),
                    pixmap.data().to_vec(),
                ),
            ));
        });

        vec![geometry]
    }
}

fn main() -> iced::Result {
    DrawingApp::run(Settings {
        window: iced::window::Settings {
            size: (1000, 700),
            ..Default::default()
        },
        ..Default::default()
    })
}

功能说明

  1. 轻量级2D渲染:iced_tiny_skia基于Tiny Skia库,提供高效的2D图形渲染能力
  2. 跨平台UI集成:可与iced UI框架无缝集成,创建跨平台的图形界面应用
  3. 简单易用:提供简洁的API用于绘制基本图形、路径和图像

1 回复

Rust图形渲染库iced_tiny_skia的使用:轻量级2D图形绘制与跨平台UI集成方案

iced_tiny_skia是一个结合了icedUI框架和tiny-skia2D图形库的Rust解决方案,提供了轻量级的2D图形绘制能力,并能无缝集成到跨平台UI应用中。

核心特性

  • 轻量级:基于高效的tiny-skia图形库
  • 跨平台:支持Windows、macOS和Linux
  • 与iced深度集成:可作为iced的图形绘制后端
  • 高性能:利用硬件加速的2D渲染
  • 简单API:提供直观的绘图接口

基本使用方法

添加依赖

首先在Cargo.toml中添加依赖:

[dependencies]
iced = "0.10"
iced_tiny_skia = "0.4"

基本绘图示例

use iced::{
    widget::canvas::{self, Canvas, Frame, Path},
    Application, Color, Command, Element, Length, Settings, Theme,
};
use iced_tiny_skia::tiny_skia;

pub fn main() -> iced::Result {
    MyApp::run(Settings::default())
}

struct MyApp;

impl Application for MyApp {
    type Message = ();
    type Theme = Theme;
    type Executor = iced::executor::Default;
    type Flags = ();

    fn new(_flags: ()) -> (Self, Command<Self::Message>) {
        (MyApp, Command::none())
    }

    fn title(&self) -> String {
        String::from("Iced + tiny-skia Demo")
    }

    fn update(&mut self, _message: Self::Message) -> Command<Self::Message> {
        Command::none()
    }

    fn view(&self) -> Element<Self::Message> {
        Canvas::new(Drawing::new())
            .width(Length::Fill)
            .height(Length::Fill)
            .into()
    }
}

struct Drawing;

impl Drawing {
    fn new() -> Self {
        Drawing
    }
}

impl canvas::Program<()> for Drawing {
    type State = ();

    fn draw(&self, _state: &Self::State, bounds: iced::Rectangle,极客时间 _cursor: canvas::Cursor) -> Vec<canvas::Geometry> {
        let mut frame = Frame::new(bounds.size());
        
        // 使用tiny-skia绘制一个简单的图形
        if let Some(pixmap) = frame.as_skia() {
            let mut paint = tiny_skia::Paint::default();
            paint.set_color_rgba8(50, 127, 150, 200);
            paint.anti_alias = true;
            
            let rect = tiny_skia::Rect::from_xywh(
                100.0, 100.0, 
                bounds.width - 200.0, 
                bounds.height - 200.0
            ).unwrap();
            
            let mut pb = tiny_skia::PathBuilder::new();
            pb.push_rect(rect);
            let path = pb.finish().unwrap();
            
            pixmap.fill_path(
                &path,
                &paint,
                tiny_skia::FillRule::Winding,
                tiny_skia::Transform::identity(),
                None,
            );
        }
        
        vec![frame.into_geometry()]
    }
}

高级功能

绘制复杂路径

// 在draw方法中添加:
let mut pb = tiny_skia::PathBuilder::new();
pb.move_to(200.0, 200.极客时间0);
pb.line_to(400.0, 200.0);
pb.quad_to(500.0, 300.0, 400.0, 400.0);
pb.line_to(200.0, 400.0);
pb.close();
let path = pb.finish().unwrap();

paint.set_color_rgba8(200, 100, 50, 220);
pixmap.fill_path(
    &path,
    &paint,
    tiny_skia::FillRule::Winding,
    tiny_skia::Transform::identity(),
    None,
);

使用渐变填充

let gradient = tiny_skia::LinearGradient::new(
    tiny_skia::Point::from_xy(100.0, 100.0),
    tiny_skia::Point::from_xy(300.0, 300.0),
    vec![
        tiny_skia::GradientStop::new(0.0, tiny_skia::Color::from_rgba8(255, 0, 0, 255)),
        tiny_skia::GradientStop::new(0.5, tiny_skia::Color::from_rgba8(0, 255, 0, 255)),
        tiny_skia::GradientStop::new(1.0, tiny_skia::Color::from_rgba8(0, 0, 255, 255)),
    ],
    tiny_skia::SpreadMode::Pad,
    tiny_skia::Transform::identity(),
).unwrap();

paint.shader = gradient;
pixmap.fill_path(
    &path,
    &paint,
    tiny_skia::FillRule::Winding,
    tiny_skia::Transform::identity(),
    None,
);

与iced UI组件集成

iced_tiny_skia可以与常规iced UI组件无缝集成:

fn view(&self) -> Element<Self::Message> {
    let column = iced::widget::column![
        iced::widget::text("这是一个iced UI与tiny-skia集成的示例")
            .size(24),
        iced::widget::button("点击我"),
        Canvas::new(Drawing::new())
            .width(Length::Fill)
            .height(Length::Fill)
    ];
    
    column.into()
}

性能优化技巧

  1. 重用Paint对象:避免在每帧创建新的Paint对象
  2. 预计算路径:对于静态图形,提前计算Path对象
  3. 限制重绘区域:使用iced::widget::canvas::Cache缓存静态图形
  4. 简化复杂路径:减少路径中的点数量

跨平台注意事项

  • 在Linux上可能需要安装额外的字体库
  • Windows平台默认支持良好
  • macOS可能需要处理Retina显示的高DPI缩放

iced_tiny_skia为Rust开发者提供了一个轻量级但功能强大的2D图形解决方案,特别适合需要在跨平台UI应用中集成自定义绘图功能的场景。

完整示例代码

下面是一个结合了基本绘图、复杂路径和渐变填充的完整示例:

use iced::{
    widget::canvas::{self, Canvas, Frame},
    Application, Command, Element, Length, Settings, Theme,
};
use iced_tiny_skia::tiny_skia;

pub fn main() -> iced::Result {
    GraphicDemo::run(Settings::default())
}

struct GraphicDemo;

impl Application for GraphicDemo {
    type Message = ();
    type Theme = Theme;
    type Executor = iced::executor::Default;
    type Flags = ();

    fn new(_flags: ()) -> (Self, Command<Self::Message>) {
        (GraphicDemo, Command::none())
    }

    fn title(&self) -> String {
        String::from("Iced tiny-skia 综合演示")
    }

    fn update(&mut self, _message: Self::Message) -> Command<Self::Message> {
        Command::none()
    }

    fn view(&self) -> Element<Self::Message> {
        Canvas::new(Graphic::new())
            .width(Length::Fill)
            .height(Length::Fill)
            .into()
    }
}

struct Graphic;

impl Graphic {
    fn new() -> Self {
        Graphic
    }
}

impl canvas::Program<()> for Graphic {
    type State = ();

    fn draw(&self, _state: &Self::State, bounds: iced::Rectangle, _cursor: canvas::Cursor) -> Vec<canvas::Geometry> {
        let mut frame = Frame::new(bounds.size());
        
        if let Some(pixmap) = frame.as_skia() {
            // 绘制一个带渐变填充的矩形背景
            let bg_rect = tiny_skia::Rect::from_xywh(0.0, 0.0, bounds.width, bounds.height).unwrap();
            let mut bg_paint = tiny_skia::Paint::default();
            
            let bg_gradient = tiny_skia::LinearGradient::new(
                tiny_skia::Point::from_xy(0.0, 0.0),
                tiny_skia::Point::from_xy(0.0, bounds.height),
                vec![
                    tiny_skia::GradientStop::new(0.0, tiny_skia::Color::from_rgba8(30, 30, 40, 255)),
                    tiny_skia::GradientStop::new(1.0, tiny_skia::Color::from_rgba8(10, 10, 20, 255)),
                ],
                tiny_skia::SpreadMode::Pad,
                tiny_skia::Transform::identity(),
            ).unwrap();
            
            bg_paint.shader = bg_gradient;
            let mut pb = tiny_skia::PathBuilder::new();
            pb.push_rect(bg_rect);
            pixmap.fill_path(&pb.finish().unwrap(), &bg_paint, tiny_skia::FillRule::Winding, tiny_skia::Transform::identity(), None);
            
            // 绘制一个复杂路径(五角星)
            let mut star_pb = tiny_skia::PathBuilder::new();
            let center_x = bounds.width / 2.0;
            let center_y = bounds.height / 2.0;
            let radius = bounds.width.min(bounds.height) * 0.4;
            
            for i in 0..5 {
                let angle = std::f32::consts::PI * 2.0 * i as f32 / 5.0 - std::f32::consts::PI / 2.0;
                let x = center_x + angle.cos() * radius;
                let y = center_y + angle.sin() * radius;
                
                if i == 0 {
                    star_pb.move_to(x, y);
                } else {
                    star_pb.line_to(x, y);
                }
                
                let inner_angle = angle + std::f32::consts::PI / 5.0;
                let inner_x = center_x + inner_angle.cos() * radius * 0.5;
                let inner_y = center_y + inner_angle.sin() * radius * 0.5;
                star_pb.line_to(inner_x, inner_y);
            }
            star_pb.close();
            
            let star_path = star_pb.finish().unwrap();
            
            // 为五角星设置渐变填充
            let mut star_paint = tiny_skia::Paint::default();
            star_paint.anti_alias = true;
            
            let star_gradient = tiny_skia::RadialGradient::new(
                tiny_skia::Point::from_xy(center_x, center_y),
                radius,
                vec![
                    tiny_skia::GradientStop::new(0.0, tiny_skia::Color::from_rgba8(255, 215, 0, 255)),
                    tiny_skia::GradientStop::new(0.7, tiny_skia::Color::from_rgba8(255, 165, 0, 255)),
                    tiny_skia::GradientStop::new(1.0, tiny_skia::Color::from_rgba8(255, 69, 0, 200)),
                ],
                tiny_skia::SpreadMode::Pad,
                tiny_skia::Transform::identity(),
            ).unwrap();
            
            star_paint.shader = star_gradient;
            pixmap.fill_path(&star_path, &star_paint, tiny_skia::FillRule::Winding, tiny_skia::Transform::identity(), None);
            
            // 添加描边效果
            let mut stroke_paint = tiny_skia::Paint::default();
            stroke_paint.set_color_rgba8(255, 255, 255, 180);
            stroke_paint.anti_alias = true;
            stroke_paint.stroke = true;
            stroke_paint.stroke_width = 2.0;
            
            pixmap.stroke_path(&star_path, &stroke_paint, &tiny_skia::Stroke::default(), tiny_skia::Transform::identity(), None);
        }
        
        vec![frame.into_geometry()]
    }
}

这个完整示例展示了:

  1. 创建渐变背景
  2. 绘制复杂形状(五角星)
  3. 使用径向渐变填充
  4. 添加描边效果
  5. 抗锯齿处理

运行后会显示一个五角星形状,带有金色到橙色的径向渐变填充和白色描边,背景是深色渐变。

回到顶部