| 1 | #![deny (missing_docs)] |
| 2 | #![cfg_attr (docsrs, feature(doc_cfg, doc_auto_cfg))] |
| 3 | |
| 4 | /*! |
| 5 | * The femtovg API is (like [NanoVG](https://github.com/memononen/nanovg)) |
| 6 | * loosely modeled on the |
| 7 | * [HTML5 Canvas API](https://bucephalus.org/text/CanvasHandbook/CanvasHandbook.html). |
| 8 | * |
| 9 | * The coordinate system’s origin is the top-left corner, |
| 10 | * with positive X rightwards, positive Y downwards. |
| 11 | */ |
| 12 | |
| 13 | /* |
| 14 | TODO: |
| 15 | - Tests |
| 16 | */ |
| 17 | |
| 18 | #[cfg (feature = "serde" )] |
| 19 | #[macro_use ] |
| 20 | extern crate serde; |
| 21 | |
| 22 | use std::{cell::RefCell, ops::Range, path::Path as FilePath, rc::Rc}; |
| 23 | |
| 24 | use imgref::ImgVec; |
| 25 | use rgb::RGBA8; |
| 26 | |
| 27 | mod text; |
| 28 | |
| 29 | mod error; |
| 30 | pub use error::ErrorKind; |
| 31 | |
| 32 | pub use text::{ |
| 33 | Align, Atlas, Baseline, DrawCommand, FontId, FontMetrics, GlyphDrawCommands, Quad, RenderMode, TextContext, |
| 34 | TextMetrics, |
| 35 | }; |
| 36 | |
| 37 | use text::{GlyphAtlas, TextContextImpl}; |
| 38 | |
| 39 | mod image; |
| 40 | use crate::image::ImageStore; |
| 41 | pub use crate::image::{ImageFilter, ImageFlags, ImageId, ImageInfo, ImageSource, PixelFormat}; |
| 42 | |
| 43 | mod color; |
| 44 | pub use color::Color; |
| 45 | |
| 46 | pub mod renderer; |
| 47 | pub use renderer::{RenderTarget, Renderer}; |
| 48 | |
| 49 | use renderer::{Command, CommandType, Drawable, Params, ShaderType, SurfacelessRenderer, Vertex}; |
| 50 | |
| 51 | pub(crate) mod geometry; |
| 52 | pub use geometry::Transform2D; |
| 53 | use geometry::*; |
| 54 | |
| 55 | mod paint; |
| 56 | pub use paint::Paint; |
| 57 | use paint::{GlyphTexture, PaintFlavor, StrokeSettings}; |
| 58 | |
| 59 | mod path; |
| 60 | use path::Convexity; |
| 61 | pub use path::{Path, PathIter, Solidity, Verb}; |
| 62 | |
| 63 | mod gradient_store; |
| 64 | use gradient_store::GradientStore; |
| 65 | |
| 66 | /// Determines the fill rule used when filling paths. |
| 67 | /// |
| 68 | /// The fill rule defines how the interior of a shape is determined. |
| 69 | #[derive (Copy, Clone, Debug, Eq, PartialEq, Default)] |
| 70 | #[cfg_attr (feature = "serde" , derive(Serialize, Deserialize))] |
| 71 | pub enum FillRule { |
| 72 | /// The interior is determined using the even-odd rule. |
| 73 | /// A point is considered inside the shape if it intersects the shape's outline an odd number of times. |
| 74 | EvenOdd, |
| 75 | /// The interior is determined using the non-zero winding rule (default). |
| 76 | /// A point is considered inside the shape if it intersects the shape's outline a non-zero number of times, |
| 77 | /// considering the direction of each intersection. |
| 78 | #[default] |
| 79 | NonZero, |
| 80 | } |
| 81 | |
| 82 | /// Blend factors. |
| 83 | #[derive (Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] |
| 84 | pub enum BlendFactor { |
| 85 | /// Not all |
| 86 | Zero, |
| 87 | /// All use |
| 88 | One, |
| 89 | /// Using the source color |
| 90 | SrcColor, |
| 91 | /// Minus the source color |
| 92 | OneMinusSrcColor, |
| 93 | /// Using the target color |
| 94 | DstColor, |
| 95 | /// Minus the target color |
| 96 | OneMinusDstColor, |
| 97 | /// Using the source alpha |
| 98 | SrcAlpha, |
| 99 | /// Minus the source alpha |
| 100 | OneMinusSrcAlpha, |
| 101 | /// Using the target alpha |
| 102 | DstAlpha, |
| 103 | /// Minus the target alpha |
| 104 | OneMinusDstAlpha, |
| 105 | /// Scale color by minimum of source alpha and destination alpha |
| 106 | SrcAlphaSaturate, |
| 107 | } |
| 108 | |
| 109 | /// Predefined composite oprations. |
| 110 | #[derive (Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] |
| 111 | pub enum CompositeOperation { |
| 112 | /// Displays the source over the destination. |
| 113 | SourceOver, |
| 114 | /// Displays the source in the destination, i.e. only the part of the source inside the destination is shown and the destination is transparent. |
| 115 | SourceIn, |
| 116 | /// Only displays the part of the source that is outside the destination, which is made transparent. |
| 117 | SourceOut, |
| 118 | /// Displays the source on top of the destination. The part of the source outside the destination is not shown. |
| 119 | Atop, |
| 120 | /// Displays the destination over the source. |
| 121 | DestinationOver, |
| 122 | /// Only displays the part of the destination that is inside the source, which is made transparent. |
| 123 | DestinationIn, |
| 124 | /// Only displays the part of the destination that is outside the source, which is made transparent. |
| 125 | DestinationOut, |
| 126 | /// Displays the destination on top of the source. The part of the destination that is outside the source is not shown. |
| 127 | DestinationAtop, |
| 128 | /// Displays the source together with the destination, the overlapping area is rendered lighter. |
| 129 | Lighter, |
| 130 | /// Ignores the destination and just displays the source. |
| 131 | Copy, |
| 132 | /// Only the areas that exclusively belong either to the destination or the source are displayed. Overlapping parts are ignored. |
| 133 | Xor, |
| 134 | } |
| 135 | |
| 136 | /// Determines how a new ("source") data is displayed against an existing ("destination") data. |
| 137 | #[derive (Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] |
| 138 | pub struct CompositeOperationState { |
| 139 | src_rgb: BlendFactor, |
| 140 | src_alpha: BlendFactor, |
| 141 | dst_rgb: BlendFactor, |
| 142 | dst_alpha: BlendFactor, |
| 143 | } |
| 144 | |
| 145 | impl CompositeOperationState { |
| 146 | /// Creates a new `CompositeOperationState` from the provided `CompositeOperation` |
| 147 | pub fn new(op: CompositeOperation) -> Self { |
| 148 | let (sfactor, dfactor) = match op { |
| 149 | CompositeOperation::SourceOver => (BlendFactor::One, BlendFactor::OneMinusSrcAlpha), |
| 150 | CompositeOperation::SourceIn => (BlendFactor::DstAlpha, BlendFactor::Zero), |
| 151 | CompositeOperation::SourceOut => (BlendFactor::OneMinusDstAlpha, BlendFactor::Zero), |
| 152 | CompositeOperation::Atop => (BlendFactor::DstAlpha, BlendFactor::OneMinusSrcAlpha), |
| 153 | CompositeOperation::DestinationOver => (BlendFactor::OneMinusDstAlpha, BlendFactor::One), |
| 154 | CompositeOperation::DestinationIn => (BlendFactor::Zero, BlendFactor::SrcAlpha), |
| 155 | CompositeOperation::DestinationOut => (BlendFactor::Zero, BlendFactor::OneMinusSrcAlpha), |
| 156 | CompositeOperation::DestinationAtop => (BlendFactor::OneMinusDstAlpha, BlendFactor::SrcAlpha), |
| 157 | CompositeOperation::Lighter => (BlendFactor::One, BlendFactor::One), |
| 158 | CompositeOperation::Copy => (BlendFactor::One, BlendFactor::Zero), |
| 159 | CompositeOperation::Xor => (BlendFactor::OneMinusDstAlpha, BlendFactor::OneMinusSrcAlpha), |
| 160 | }; |
| 161 | |
| 162 | Self { |
| 163 | src_rgb: sfactor, |
| 164 | src_alpha: sfactor, |
| 165 | dst_rgb: dfactor, |
| 166 | dst_alpha: dfactor, |
| 167 | } |
| 168 | } |
| 169 | |
| 170 | /// Creates a new `CompositeOperationState` with source and destination blend factors. |
| 171 | pub fn with_blend_factors(src_factor: BlendFactor, dst_factor: BlendFactor) -> Self { |
| 172 | Self { |
| 173 | src_rgb: src_factor, |
| 174 | src_alpha: src_factor, |
| 175 | dst_rgb: dst_factor, |
| 176 | dst_alpha: dst_factor, |
| 177 | } |
| 178 | } |
| 179 | } |
| 180 | |
| 181 | impl Default for CompositeOperationState { |
| 182 | fn default() -> Self { |
| 183 | Self::new(op:CompositeOperation::SourceOver) |
| 184 | } |
| 185 | } |
| 186 | |
| 187 | #[derive (Copy, Clone, Debug, Default)] |
| 188 | struct Scissor { |
| 189 | transform: Transform2D, |
| 190 | extent: Option<[f32; 2]>, |
| 191 | } |
| 192 | |
| 193 | impl Scissor { |
| 194 | /// Returns the bounding rect if the scissor clip if it's an untransformed rectangular clip |
| 195 | fn as_rect(&self, canvas_width: f32, canvas_height: f32) -> Option<Rect> { |
| 196 | let Some(extent) = self.extent else { |
| 197 | return Some(Rect::new(0., 0., canvas_width, canvas_height)); |
| 198 | }; |
| 199 | |
| 200 | let Transform2D([a, b, c, d, x, y]) = self.transform; |
| 201 | |
| 202 | // Abort if we're skewing (usually doesn't happen) |
| 203 | if b != 0.0 || c != 0.0 { |
| 204 | return None; |
| 205 | } |
| 206 | |
| 207 | // Abort if we're scaling |
| 208 | if a != 1.0 || d != 1.0 { |
| 209 | return None; |
| 210 | } |
| 211 | |
| 212 | let half_width = extent[0]; |
| 213 | let half_height = extent[1]; |
| 214 | Some(Rect::new( |
| 215 | x - half_width, |
| 216 | y - half_height, |
| 217 | half_width * 2.0, |
| 218 | half_height * 2.0, |
| 219 | )) |
| 220 | } |
| 221 | } |
| 222 | |
| 223 | /// Determines the shape used to draw the end points of lines. |
| 224 | /// |
| 225 | /// The default value is `Butt`. |
| 226 | #[derive (Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Default)] |
| 227 | #[cfg_attr (feature = "serde" , derive(Serialize, Deserialize))] |
| 228 | pub enum LineCap { |
| 229 | /// The ends of lines are squared off at the endpoints. |
| 230 | #[default] |
| 231 | Butt, |
| 232 | /// The ends of lines are rounded. |
| 233 | Round, |
| 234 | /// The ends of lines are squared off by adding a box with an equal |
| 235 | /// width and half the height of the line's thickness. |
| 236 | Square, |
| 237 | } |
| 238 | |
| 239 | /// Determines the shape used to join two line segments where they meet. |
| 240 | /// |
| 241 | /// The default value is `Miter`. |
| 242 | #[derive (Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Default)] |
| 243 | #[cfg_attr (feature = "serde" , derive(Serialize, Deserialize))] |
| 244 | pub enum LineJoin { |
| 245 | /// Connected segments are joined by extending their outside edges to |
| 246 | /// connect at a single point, with the effect of filling an additional |
| 247 | /// lozenge-shaped area. This setting is affected by the miterLimit property. |
| 248 | #[default] |
| 249 | Miter, |
| 250 | /// Rounds off the corners of a shape by filling an additional sector |
| 251 | /// of disc centered at the common endpoint of connected segments. |
| 252 | /// The radius for these rounded corners is equal to the line width. |
| 253 | Round, |
| 254 | /// Fills an additional triangular area between the common endpoint |
| 255 | /// of connected segments, and the separate outside rectangular |
| 256 | /// corners of each segment. |
| 257 | Bevel, |
| 258 | } |
| 259 | |
| 260 | #[derive (Copy, Clone, Debug)] |
| 261 | struct State { |
| 262 | composite_operation: CompositeOperationState, |
| 263 | transform: Transform2D, |
| 264 | scissor: Scissor, |
| 265 | alpha: f32, |
| 266 | } |
| 267 | |
| 268 | impl Default for State { |
| 269 | fn default() -> Self { |
| 270 | Self { |
| 271 | composite_operation: Default::default(), |
| 272 | transform: Transform2D::identity(), |
| 273 | scissor: Default::default(), |
| 274 | alpha: 1.0, |
| 275 | } |
| 276 | } |
| 277 | } |
| 278 | |
| 279 | /// Main 2D drawing context. |
| 280 | pub struct Canvas<T: Renderer> { |
| 281 | width: u32, |
| 282 | height: u32, |
| 283 | renderer: T, |
| 284 | text_context: Rc<RefCell<TextContextImpl>>, |
| 285 | glyph_atlas: Rc<GlyphAtlas>, |
| 286 | // Glyph atlas used for direct rendering of color glyphs, dropped after flush() |
| 287 | ephemeral_glyph_atlas: Option<Rc<GlyphAtlas>>, |
| 288 | current_render_target: RenderTarget, |
| 289 | state_stack: Vec<State>, |
| 290 | commands: Vec<Command>, |
| 291 | verts: Vec<Vertex>, |
| 292 | images: ImageStore<T::Image>, |
| 293 | fringe_width: f32, |
| 294 | device_px_ratio: f32, |
| 295 | tess_tol: f32, |
| 296 | dist_tol: f32, |
| 297 | gradients: GradientStore, |
| 298 | } |
| 299 | |
| 300 | impl<T> Canvas<T> |
| 301 | where |
| 302 | T: Renderer, |
| 303 | { |
| 304 | /// Creates a new canvas. |
| 305 | pub fn new(renderer: T) -> Result<Self, ErrorKind> { |
| 306 | let mut canvas = Self { |
| 307 | width: 0, |
| 308 | height: 0, |
| 309 | renderer, |
| 310 | text_context: Default::default(), |
| 311 | glyph_atlas: Default::default(), |
| 312 | ephemeral_glyph_atlas: None, |
| 313 | current_render_target: RenderTarget::Screen, |
| 314 | state_stack: Vec::new(), |
| 315 | commands: Vec::new(), |
| 316 | verts: Vec::new(), |
| 317 | images: ImageStore::new(), |
| 318 | fringe_width: 1.0, |
| 319 | device_px_ratio: 1.0, |
| 320 | tess_tol: 0.25, |
| 321 | dist_tol: 0.01, |
| 322 | gradients: GradientStore::new(), |
| 323 | }; |
| 324 | |
| 325 | canvas.save(); |
| 326 | |
| 327 | Ok(canvas) |
| 328 | } |
| 329 | |
| 330 | /// Creates a new canvas with the specified renderer and using the fonts registered with the |
| 331 | /// provided [`TextContext`]. Note that the context is explicitly shared, so that any fonts |
| 332 | /// registered with a clone of this context will also be visible to this canvas. |
| 333 | pub fn new_with_text_context(renderer: T, text_context: TextContext) -> Result<Self, ErrorKind> { |
| 334 | let mut canvas = Self { |
| 335 | width: 0, |
| 336 | height: 0, |
| 337 | renderer, |
| 338 | text_context: text_context.0, |
| 339 | glyph_atlas: Default::default(), |
| 340 | ephemeral_glyph_atlas: None, |
| 341 | current_render_target: RenderTarget::Screen, |
| 342 | state_stack: Vec::new(), |
| 343 | commands: Vec::new(), |
| 344 | verts: Vec::new(), |
| 345 | images: ImageStore::new(), |
| 346 | fringe_width: 1.0, |
| 347 | device_px_ratio: 1.0, |
| 348 | tess_tol: 0.25, |
| 349 | dist_tol: 0.01, |
| 350 | gradients: GradientStore::new(), |
| 351 | }; |
| 352 | |
| 353 | canvas.save(); |
| 354 | |
| 355 | Ok(canvas) |
| 356 | } |
| 357 | |
| 358 | /// Sets the size of the default framebuffer (screen size) |
| 359 | pub fn set_size(&mut self, width: u32, height: u32, dpi: f32) { |
| 360 | self.width = width; |
| 361 | self.height = height; |
| 362 | self.fringe_width = 1.0 / dpi; |
| 363 | self.tess_tol = 0.25 / dpi; |
| 364 | self.dist_tol = 0.01 / dpi; |
| 365 | self.device_px_ratio = dpi; |
| 366 | |
| 367 | self.renderer.set_size(width, height, dpi); |
| 368 | |
| 369 | self.append_cmd(Command::new(CommandType::SetRenderTarget(RenderTarget::Screen))); |
| 370 | } |
| 371 | |
| 372 | /// Clears the rectangle area defined by left upper corner (x,y), width and height with the provided color. |
| 373 | pub fn clear_rect(&mut self, x: u32, y: u32, width: u32, height: u32, color: Color) { |
| 374 | let mut cmd = Command::new(CommandType::ClearRect { color }); |
| 375 | cmd.composite_operation = self.state().composite_operation; |
| 376 | |
| 377 | let x0 = x as f32; |
| 378 | let y0 = y as f32; |
| 379 | let x1 = x0 + width as f32; |
| 380 | let y1 = y0 + height as f32; |
| 381 | |
| 382 | let (p0, p1) = (x0, y0); |
| 383 | let (p2, p3) = (x1, y0); |
| 384 | let (p4, p5) = (x1, y1); |
| 385 | let (p6, p7) = (x0, y1); |
| 386 | |
| 387 | let verts = [ |
| 388 | Vertex::new(p0, p1, 0.0, 0.0), |
| 389 | Vertex::new(p4, p5, 0.0, 0.0), |
| 390 | Vertex::new(p2, p3, 0.0, 0.0), |
| 391 | Vertex::new(p0, p1, 0.0, 0.0), |
| 392 | Vertex::new(p6, p7, 0.0, 0.0), |
| 393 | Vertex::new(p4, p5, 0.0, 0.0), |
| 394 | ]; |
| 395 | |
| 396 | cmd.triangles_verts = Some((self.verts.len(), verts.len())); |
| 397 | self.append_cmd(cmd); |
| 398 | |
| 399 | self.verts.extend_from_slice(&verts); |
| 400 | } |
| 401 | |
| 402 | /// Returns the width of the current render target. |
| 403 | pub fn width(&self) -> u32 { |
| 404 | match self.current_render_target { |
| 405 | RenderTarget::Image(id) => self.image_info(id).map(|info| info.width() as u32).unwrap_or(0), |
| 406 | RenderTarget::Screen => self.width, |
| 407 | } |
| 408 | } |
| 409 | |
| 410 | /// Returns the height of the current render target. |
| 411 | pub fn height(&self) -> u32 { |
| 412 | match self.current_render_target { |
| 413 | RenderTarget::Image(id) => self.image_info(id).map(|info| info.height() as u32).unwrap_or(0), |
| 414 | RenderTarget::Screen => self.height, |
| 415 | } |
| 416 | } |
| 417 | |
| 418 | /// Tells the renderer to execute all drawing commands and clears the current internal state |
| 419 | /// |
| 420 | /// Call this at the end of each frame. |
| 421 | pub fn flush_to_surface(&mut self, surface: &T::Surface) -> T::CommandBuffer { |
| 422 | let command_buffer = self.renderer.render( |
| 423 | surface, |
| 424 | &mut self.images, |
| 425 | &self.verts, |
| 426 | std::mem::take(&mut self.commands), |
| 427 | ); |
| 428 | self.verts.clear(); |
| 429 | self.gradients |
| 430 | .release_old_gradients(&mut self.images, &mut self.renderer); |
| 431 | if let Some(atlas) = self.ephemeral_glyph_atlas.take() { |
| 432 | atlas.clear(self); |
| 433 | } |
| 434 | command_buffer |
| 435 | } |
| 436 | |
| 437 | /// Returns a screenshot of the current canvas. |
| 438 | pub fn screenshot(&mut self) -> Result<ImgVec<RGBA8>, ErrorKind> { |
| 439 | self.renderer.screenshot() |
| 440 | } |
| 441 | |
| 442 | // State Handling |
| 443 | |
| 444 | /// Pushes and saves the current render state into a state stack. |
| 445 | /// |
| 446 | /// A matching `restore()` must be used to restore the state. |
| 447 | pub fn save(&mut self) { |
| 448 | let state = self.state_stack.last().map_or_else(State::default, |state| *state); |
| 449 | |
| 450 | self.state_stack.push(state); |
| 451 | } |
| 452 | |
| 453 | /// Restores the previous render state |
| 454 | /// |
| 455 | /// Restoring the initial/first state will just reset it to the defaults |
| 456 | pub fn restore(&mut self) { |
| 457 | if self.state_stack.len() > 1 { |
| 458 | self.state_stack.pop(); |
| 459 | } else { |
| 460 | self.reset(); |
| 461 | } |
| 462 | } |
| 463 | |
| 464 | /// Resets current state to default values. Does not affect the state stack. |
| 465 | pub fn reset(&mut self) { |
| 466 | *self.state_mut() = Default::default(); |
| 467 | } |
| 468 | |
| 469 | /// Saves the current state before calling the callback and restores it afterwards |
| 470 | /// |
| 471 | /// This is less error prone than remembering to match `save()` -> `restore()` calls |
| 472 | pub fn save_with(&mut self, mut callback: impl FnMut(&mut Self)) { |
| 473 | self.save(); |
| 474 | |
| 475 | callback(self); |
| 476 | |
| 477 | self.restore(); |
| 478 | } |
| 479 | |
| 480 | // Render styles |
| 481 | |
| 482 | /// Sets the transparency applied to all rendered shapes. |
| 483 | /// |
| 484 | /// Already transparent paths will get proportionally more transparent as well. |
| 485 | pub fn set_global_alpha(&mut self, alpha: f32) { |
| 486 | self.state_mut().alpha = alpha; |
| 487 | } |
| 488 | |
| 489 | /// Sets the composite operation. |
| 490 | pub fn global_composite_operation(&mut self, op: CompositeOperation) { |
| 491 | self.state_mut().composite_operation = CompositeOperationState::new(op); |
| 492 | } |
| 493 | |
| 494 | /// Sets the composite operation with custom pixel arithmetic. |
| 495 | pub fn global_composite_blend_func(&mut self, src_factor: BlendFactor, dst_factor: BlendFactor) { |
| 496 | self.global_composite_blend_func_separate(src_factor, dst_factor, src_factor, dst_factor); |
| 497 | } |
| 498 | |
| 499 | /// Sets the composite operation with custom pixel arithmetic for RGB and alpha components separately. |
| 500 | pub fn global_composite_blend_func_separate( |
| 501 | &mut self, |
| 502 | src_rgb: BlendFactor, |
| 503 | dst_rgb: BlendFactor, |
| 504 | src_alpha: BlendFactor, |
| 505 | dst_alpha: BlendFactor, |
| 506 | ) { |
| 507 | self.state_mut().composite_operation = CompositeOperationState { |
| 508 | src_rgb, |
| 509 | src_alpha, |
| 510 | dst_rgb, |
| 511 | dst_alpha, |
| 512 | } |
| 513 | } |
| 514 | |
| 515 | /// Sets a new render target. All drawing operations after this call will happen on the provided render target |
| 516 | pub fn set_render_target(&mut self, target: RenderTarget) { |
| 517 | if self.current_render_target != target { |
| 518 | self.append_cmd(Command::new(CommandType::SetRenderTarget(target))); |
| 519 | self.current_render_target = target; |
| 520 | } |
| 521 | } |
| 522 | |
| 523 | fn append_cmd(&mut self, cmd: Command) { |
| 524 | self.commands.push(cmd); |
| 525 | } |
| 526 | |
| 527 | // Images |
| 528 | |
| 529 | /// Allocates an empty image with the provided domensions and format. |
| 530 | pub fn create_image_empty( |
| 531 | &mut self, |
| 532 | width: usize, |
| 533 | height: usize, |
| 534 | format: PixelFormat, |
| 535 | flags: ImageFlags, |
| 536 | ) -> Result<ImageId, ErrorKind> { |
| 537 | let info = ImageInfo::new(flags, width, height, format); |
| 538 | |
| 539 | self.images.alloc(&mut self.renderer, info) |
| 540 | } |
| 541 | |
| 542 | /// Allocates an image that wraps the given backend-specific texture. |
| 543 | /// Use this function to import external textures into the rendering of a scene |
| 544 | /// with femtovg. |
| 545 | /// |
| 546 | /// It is necessary to call `[Self::delete_image`] to free femtovg specific |
| 547 | /// book-keeping data structures, the underlying backend-specific texture memory |
| 548 | /// will not be freed. It is the caller's responsible to delete it. |
| 549 | pub fn create_image_from_native_texture( |
| 550 | &mut self, |
| 551 | texture: T::NativeTexture, |
| 552 | info: ImageInfo, |
| 553 | ) -> Result<ImageId, ErrorKind> { |
| 554 | self.images.register_native_texture(&mut self.renderer, texture, info) |
| 555 | } |
| 556 | |
| 557 | /// Creates image from specified image data. |
| 558 | pub fn create_image<'a, S: Into<ImageSource<'a>>>( |
| 559 | &mut self, |
| 560 | src: S, |
| 561 | flags: ImageFlags, |
| 562 | ) -> Result<ImageId, ErrorKind> { |
| 563 | let src = src.into(); |
| 564 | let size = src.dimensions(); |
| 565 | let id = self.create_image_empty(size.width, size.height, src.format(), flags)?; |
| 566 | self.images.update(&mut self.renderer, id, src, 0, 0)?; |
| 567 | Ok(id) |
| 568 | } |
| 569 | |
| 570 | /// Returns the native texture of an image given its ID. |
| 571 | pub fn get_native_texture(&self, id: ImageId) -> Result<T::NativeTexture, ErrorKind> { |
| 572 | self.get_image(id) |
| 573 | .ok_or(ErrorKind::ImageIdNotFound) |
| 574 | .and_then(|image| self.renderer.get_native_texture(image)) |
| 575 | } |
| 576 | |
| 577 | /// Retrieves a reference to the image with the specified ID. |
| 578 | pub fn get_image(&self, id: ImageId) -> Option<&T::Image> { |
| 579 | self.images.get(id) |
| 580 | } |
| 581 | |
| 582 | /// Retrieves a mutable reference to the image with the specified ID. |
| 583 | pub fn get_image_mut(&mut self, id: ImageId) -> Option<&mut T::Image> { |
| 584 | self.images.get_mut(id) |
| 585 | } |
| 586 | |
| 587 | /// Resizes an image to the new provided dimensions. |
| 588 | pub fn realloc_image( |
| 589 | &mut self, |
| 590 | id: ImageId, |
| 591 | width: usize, |
| 592 | height: usize, |
| 593 | format: PixelFormat, |
| 594 | flags: ImageFlags, |
| 595 | ) -> Result<(), ErrorKind> { |
| 596 | let info = ImageInfo::new(flags, width, height, format); |
| 597 | self.images.realloc(&mut self.renderer, id, info) |
| 598 | } |
| 599 | |
| 600 | /// Decode an image from file |
| 601 | #[cfg (feature = "image-loading" )] |
| 602 | pub fn load_image_file<P: AsRef<FilePath>>( |
| 603 | &mut self, |
| 604 | filename: P, |
| 605 | flags: ImageFlags, |
| 606 | ) -> Result<ImageId, ErrorKind> { |
| 607 | let image = ::image::open(filename)?; |
| 608 | |
| 609 | let src = ImageSource::try_from(&image)?; |
| 610 | |
| 611 | self.create_image(src, flags) |
| 612 | } |
| 613 | |
| 614 | /// Decode an image from memory |
| 615 | #[cfg (feature = "image-loading" )] |
| 616 | pub fn load_image_mem(&mut self, data: &[u8], flags: ImageFlags) -> Result<ImageId, ErrorKind> { |
| 617 | let image = ::image::load_from_memory(data)?; |
| 618 | |
| 619 | let src = ImageSource::try_from(&image)?; |
| 620 | |
| 621 | self.create_image(src, flags) |
| 622 | } |
| 623 | |
| 624 | /// Updates image data specified by image handle. |
| 625 | pub fn update_image<'a, S: Into<ImageSource<'a>>>( |
| 626 | &mut self, |
| 627 | id: ImageId, |
| 628 | src: S, |
| 629 | x: usize, |
| 630 | y: usize, |
| 631 | ) -> Result<(), ErrorKind> { |
| 632 | self.images.update(&mut self.renderer, id, src.into(), x, y) |
| 633 | } |
| 634 | |
| 635 | /// Deletes created image. |
| 636 | pub fn delete_image(&mut self, id: ImageId) { |
| 637 | self.images.remove(&mut self.renderer, id); |
| 638 | } |
| 639 | |
| 640 | /// Returns image info |
| 641 | pub fn image_info(&self, id: ImageId) -> Result<ImageInfo, ErrorKind> { |
| 642 | if let Some(info) = self.images.info(id) { |
| 643 | Ok(info) |
| 644 | } else { |
| 645 | Err(ErrorKind::ImageIdNotFound) |
| 646 | } |
| 647 | } |
| 648 | |
| 649 | /// Returns the size in pixels of the image for the specified id. |
| 650 | pub fn image_size(&self, id: ImageId) -> Result<(usize, usize), ErrorKind> { |
| 651 | let info = self.image_info(id)?; |
| 652 | Ok((info.width(), info.height())) |
| 653 | } |
| 654 | |
| 655 | /// Renders the given `source_image` into `target_image` while applying a filter effect. |
| 656 | /// |
| 657 | /// The target image must have the same size as the source image. The filtering is recorded |
| 658 | /// as a drawing command and run by the renderer when [`Self::flush()`] is called. |
| 659 | /// |
| 660 | /// The filtering does not take any transformation set on the Canvas into account nor does it |
| 661 | /// change the current rendering target. |
| 662 | pub fn filter_image(&mut self, target_image: ImageId, filter: ImageFilter, source_image: ImageId) { |
| 663 | let Ok((image_width, image_height)) = self.image_size(source_image) else { |
| 664 | return; |
| 665 | }; |
| 666 | |
| 667 | // The renderer will receive a RenderFilteredImage command with two triangles attached that |
| 668 | // cover the image and the source image. |
| 669 | let mut cmd = Command::new(CommandType::RenderFilteredImage { target_image, filter }); |
| 670 | cmd.image = Some(source_image); |
| 671 | |
| 672 | let vertex_offset = self.verts.len(); |
| 673 | |
| 674 | let image_width = image_width as f32; |
| 675 | let image_height = image_height as f32; |
| 676 | |
| 677 | let quad_x0 = 0.0; |
| 678 | let quad_y0 = -image_height; |
| 679 | let quad_x1 = image_width; |
| 680 | let quad_y1 = image_height; |
| 681 | |
| 682 | let texture_x0 = -(image_width / 2.); |
| 683 | let texture_y0 = -(image_height / 2.); |
| 684 | let texture_x1 = (image_width) / 2.; |
| 685 | let texture_y1 = (image_height) / 2.; |
| 686 | |
| 687 | self.verts.push(Vertex::new(quad_x0, quad_y0, texture_x0, texture_y0)); |
| 688 | self.verts.push(Vertex::new(quad_x1, quad_y1, texture_x1, texture_y1)); |
| 689 | self.verts.push(Vertex::new(quad_x1, quad_y0, texture_x1, texture_y0)); |
| 690 | self.verts.push(Vertex::new(quad_x0, quad_y0, texture_x0, texture_y0)); |
| 691 | self.verts.push(Vertex::new(quad_x0, quad_y1, texture_x0, texture_y1)); |
| 692 | self.verts.push(Vertex::new(quad_x1, quad_y1, texture_x1, texture_y1)); |
| 693 | |
| 694 | cmd.triangles_verts = Some((vertex_offset, 6)); |
| 695 | |
| 696 | self.append_cmd(cmd) |
| 697 | } |
| 698 | |
| 699 | // Transforms |
| 700 | |
| 701 | /// Resets current transform to a identity matrix. |
| 702 | pub fn reset_transform(&mut self) { |
| 703 | self.state_mut().transform = Transform2D::identity(); |
| 704 | } |
| 705 | |
| 706 | #[allow (clippy::many_single_char_names)] |
| 707 | /// Premultiplies current coordinate system by specified transform. |
| 708 | pub fn set_transform(&mut self, transform: &Transform2D) { |
| 709 | self.state_mut().transform.premultiply(transform); |
| 710 | } |
| 711 | |
| 712 | /// Translates the current coordinate system. |
| 713 | pub fn translate(&mut self, x: f32, y: f32) { |
| 714 | let t = Transform2D::translation(x, y); |
| 715 | self.state_mut().transform.premultiply(&t); |
| 716 | } |
| 717 | |
| 718 | /// Rotates the current coordinate system. Angle is specified in radians. |
| 719 | pub fn rotate(&mut self, angle: f32) { |
| 720 | let t = Transform2D::rotation(angle); |
| 721 | self.state_mut().transform.premultiply(&t); |
| 722 | } |
| 723 | |
| 724 | /// Scales the current coordinate system. |
| 725 | pub fn scale(&mut self, x: f32, y: f32) { |
| 726 | let t = Transform2D::scaling(x, y); |
| 727 | self.state_mut().transform.premultiply(&t); |
| 728 | } |
| 729 | |
| 730 | /// Skews the current coordinate system along X axis. Angle is specified in radians. |
| 731 | pub fn skew_x(&mut self, angle: f32) { |
| 732 | let mut t = Transform2D::identity(); |
| 733 | t.skew_x(angle); |
| 734 | self.state_mut().transform.premultiply(&t); |
| 735 | } |
| 736 | |
| 737 | /// Skews the current coordinate system along Y axis. Angle is specified in radians. |
| 738 | pub fn skew_y(&mut self, angle: f32) { |
| 739 | let mut t = Transform2D::identity(); |
| 740 | t.skew_y(angle); |
| 741 | self.state_mut().transform.premultiply(&t); |
| 742 | } |
| 743 | |
| 744 | /// Returns the current transformation matrix |
| 745 | pub fn transform(&self) -> Transform2D { |
| 746 | self.state().transform |
| 747 | } |
| 748 | |
| 749 | // Scissoring |
| 750 | |
| 751 | /// Sets the current scissor rectangle. |
| 752 | /// |
| 753 | /// The scissor rectangle is transformed by the current transform. |
| 754 | pub fn scissor(&mut self, x: f32, y: f32, w: f32, h: f32) { |
| 755 | let state = self.state_mut(); |
| 756 | |
| 757 | let w = w.max(0.0); |
| 758 | let h = h.max(0.0); |
| 759 | |
| 760 | let mut transform = Transform2D::translation(x + w * 0.5, y + h * 0.5); |
| 761 | transform *= state.transform; |
| 762 | state.scissor.transform = transform; |
| 763 | |
| 764 | state.scissor.extent = Some([w * 0.5, h * 0.5]); |
| 765 | } |
| 766 | |
| 767 | /// Intersects current scissor rectangle with the specified rectangle. |
| 768 | /// |
| 769 | /// The scissor rectangle is transformed by the current transform. |
| 770 | /// Note: in case the rotation of previous scissor rect differs from |
| 771 | /// the current one, the intersection will be done between the specified |
| 772 | /// rectangle and the previous scissor rectangle transformed in the current |
| 773 | /// transform space. The resulting shape is always rectangle. |
| 774 | pub fn intersect_scissor(&mut self, x: f32, y: f32, w: f32, h: f32) { |
| 775 | let state = self.state_mut(); |
| 776 | |
| 777 | // If no previous scissor has been set, set the scissor as current scissor. |
| 778 | if state.scissor.extent.is_none() { |
| 779 | self.scissor(x, y, w, h); |
| 780 | return; |
| 781 | } |
| 782 | |
| 783 | let extent = state.scissor.extent.unwrap(); |
| 784 | |
| 785 | // Transform the current scissor rect into current transform space. |
| 786 | // If there is difference in rotation, this will be approximation. |
| 787 | |
| 788 | let Transform2D([a, b, c, d, tx, ty]) = state.scissor.transform / state.transform; |
| 789 | |
| 790 | let ex = extent[0]; |
| 791 | let ey = extent[1]; |
| 792 | |
| 793 | let tex = ex * a.abs() + ey * c.abs(); |
| 794 | let tey = ex * b.abs() + ey * d.abs(); |
| 795 | |
| 796 | let rect = Rect::new(tx - tex, ty - tey, tex * 2.0, tey * 2.0); |
| 797 | let res = rect.intersect(Rect::new(x, y, w, h)); |
| 798 | |
| 799 | self.scissor(res.x, res.y, res.w, res.h); |
| 800 | } |
| 801 | |
| 802 | /// Reset and disables scissoring. |
| 803 | pub fn reset_scissor(&mut self) { |
| 804 | self.state_mut().scissor = Scissor::default(); |
| 805 | } |
| 806 | |
| 807 | // Paths |
| 808 | |
| 809 | /// Returns true if the specified point (x,y) is in the provided path, and false otherwise. |
| 810 | pub fn contains_point(&self, path: &Path, x: f32, y: f32, fill_rule: FillRule) -> bool { |
| 811 | let transform = self.state().transform; |
| 812 | |
| 813 | // The path cache saves a flattened and transformed version of the path. |
| 814 | let path_cache = path.cache(&transform, self.tess_tol, self.dist_tol); |
| 815 | |
| 816 | // Early out if path is outside the canvas bounds |
| 817 | if path_cache.bounds.maxx < 0.0 |
| 818 | || path_cache.bounds.minx > self.width() as f32 |
| 819 | || path_cache.bounds.maxy < 0.0 |
| 820 | || path_cache.bounds.miny > self.height() as f32 |
| 821 | { |
| 822 | return false; |
| 823 | } |
| 824 | |
| 825 | path_cache.contains_point(x, y, fill_rule) |
| 826 | } |
| 827 | |
| 828 | /// Return the bounding box for a Path |
| 829 | pub fn path_bbox(&self, path: &Path) -> Bounds { |
| 830 | let transform = self.state().transform; |
| 831 | |
| 832 | // The path cache saves a flattened and transformed version of the path. |
| 833 | let path_cache = path.cache(&transform, self.tess_tol, self.dist_tol); |
| 834 | |
| 835 | path_cache.bounds |
| 836 | } |
| 837 | |
| 838 | /// Fills the provided Path with the specified Paint. |
| 839 | pub fn fill_path(&mut self, path: &Path, paint: &Paint) { |
| 840 | self.fill_path_internal(path, &paint.flavor, paint.shape_anti_alias, paint.fill_rule); |
| 841 | } |
| 842 | |
| 843 | fn fill_path_internal(&mut self, path: &Path, paint_flavor: &PaintFlavor, anti_alias: bool, fill_rule: FillRule) { |
| 844 | let mut paint_flavor = paint_flavor.clone(); |
| 845 | let transform = self.state().transform; |
| 846 | |
| 847 | // The path cache saves a flattened and transformed version of the path. |
| 848 | let mut path_cache = path.cache(&transform, self.tess_tol, self.dist_tol); |
| 849 | |
| 850 | let canvas_width = self.width(); |
| 851 | let canvas_height = self.height(); |
| 852 | |
| 853 | // Early out if path is outside the canvas bounds |
| 854 | if path_cache.bounds.maxx < 0.0 |
| 855 | || path_cache.bounds.minx > canvas_width as f32 |
| 856 | || path_cache.bounds.maxy < 0.0 |
| 857 | || path_cache.bounds.miny > canvas_height as f32 |
| 858 | { |
| 859 | return; |
| 860 | } |
| 861 | |
| 862 | // Apply global alpha |
| 863 | paint_flavor.mul_alpha(self.state().alpha); |
| 864 | |
| 865 | let scissor = self.state().scissor; |
| 866 | |
| 867 | // Calculate fill vertices. |
| 868 | // expand_fill will fill path_cache.contours[].{stroke, fill} with vertex data for the GPU |
| 869 | // fringe_with is the size of the strip of triangles generated at the path border used for AA |
| 870 | let fringe_width = if anti_alias { self.fringe_width } else { 0.0 }; |
| 871 | path_cache.expand_fill(fringe_width, LineJoin::Miter, 2.4); |
| 872 | |
| 873 | // Detect if this path fill is in fact just an unclipped image copy |
| 874 | |
| 875 | if let (Some(path_rect), Some(scissor_rect), true) = ( |
| 876 | path_cache.path_fill_is_rect(), |
| 877 | scissor.as_rect(canvas_width as f32, canvas_height as f32), |
| 878 | paint_flavor.is_straight_tinted_image(anti_alias), |
| 879 | ) { |
| 880 | if scissor_rect.contains_rect(&path_rect) { |
| 881 | self.render_unclipped_image_blit(&path_rect, &transform, &paint_flavor); |
| 882 | } else if let Some(intersection) = path_rect.intersection(&scissor_rect) { |
| 883 | self.render_unclipped_image_blit(&intersection, &transform, &paint_flavor); |
| 884 | } |
| 885 | |
| 886 | return; |
| 887 | } |
| 888 | |
| 889 | // GPU uniforms |
| 890 | let flavor = if path_cache.contours.len() == 1 && path_cache.contours[0].convexity == Convexity::Convex { |
| 891 | let params = Params::new( |
| 892 | &self.images, |
| 893 | &transform, |
| 894 | &paint_flavor, |
| 895 | &GlyphTexture::default(), |
| 896 | &scissor, |
| 897 | self.fringe_width, |
| 898 | self.fringe_width, |
| 899 | -1.0, |
| 900 | ); |
| 901 | |
| 902 | CommandType::ConvexFill { params } |
| 903 | } else { |
| 904 | let stencil_params = Params { |
| 905 | stroke_thr: -1.0, |
| 906 | shader_type: ShaderType::Stencil, |
| 907 | ..Params::default() |
| 908 | }; |
| 909 | |
| 910 | let fill_params = Params::new( |
| 911 | &self.images, |
| 912 | &transform, |
| 913 | &paint_flavor, |
| 914 | &GlyphTexture::default(), |
| 915 | &scissor, |
| 916 | self.fringe_width, |
| 917 | self.fringe_width, |
| 918 | -1.0, |
| 919 | ); |
| 920 | |
| 921 | CommandType::ConcaveFill { |
| 922 | stencil_params, |
| 923 | fill_params, |
| 924 | } |
| 925 | }; |
| 926 | |
| 927 | // GPU command |
| 928 | let mut cmd = Command::new(flavor); |
| 929 | cmd.fill_rule = fill_rule; |
| 930 | cmd.composite_operation = self.state().composite_operation; |
| 931 | |
| 932 | if let PaintFlavor::Image { id, .. } = paint_flavor { |
| 933 | cmd.image = Some(id); |
| 934 | } else if let Some(paint::GradientColors::MultiStop { stops }) = paint_flavor.gradient_colors() { |
| 935 | cmd.image = self |
| 936 | .gradients |
| 937 | .lookup_or_add(stops, &mut self.images, &mut self.renderer) |
| 938 | .ok(); |
| 939 | } |
| 940 | |
| 941 | // All verts from all shapes are kept in a single buffer here in the canvas. |
| 942 | // Drawable struct is used to describe the range of vertices each draw call will operate on |
| 943 | let mut offset = self.verts.len(); |
| 944 | |
| 945 | cmd.drawables.reserve_exact(path_cache.contours.len()); |
| 946 | for contour in &path_cache.contours { |
| 947 | let mut drawable = Drawable::default(); |
| 948 | |
| 949 | // Fill commands can have both fill and stroke vertices. Fill vertices are used to fill |
| 950 | // the body of the shape while stroke vertices are used to prodice antialiased edges |
| 951 | |
| 952 | if !contour.fill.is_empty() { |
| 953 | drawable.fill_verts = Some((offset, contour.fill.len())); |
| 954 | self.verts.extend_from_slice(&contour.fill); |
| 955 | offset += contour.fill.len(); |
| 956 | } |
| 957 | |
| 958 | if !contour.stroke.is_empty() { |
| 959 | drawable.stroke_verts = Some((offset, contour.stroke.len())); |
| 960 | self.verts.extend_from_slice(&contour.stroke); |
| 961 | offset += contour.stroke.len(); |
| 962 | } |
| 963 | |
| 964 | cmd.drawables.push(drawable); |
| 965 | } |
| 966 | |
| 967 | if let CommandType::ConcaveFill { .. } = cmd.cmd_type { |
| 968 | // Concave shapes are first filled by writing to a stencil buffer and then drawing a quad |
| 969 | // over the shape area with stencil test enabled to produce the final fill. These are |
| 970 | // the verts needed for the covering quad |
| 971 | self.verts.push(Vertex::new( |
| 972 | path_cache.bounds.maxx + fringe_width, |
| 973 | path_cache.bounds.maxy + fringe_width, |
| 974 | 0.5, |
| 975 | 1.0, |
| 976 | )); |
| 977 | self.verts.push(Vertex::new( |
| 978 | path_cache.bounds.maxx + fringe_width, |
| 979 | path_cache.bounds.miny - fringe_width, |
| 980 | 0.5, |
| 981 | 1.0, |
| 982 | )); |
| 983 | self.verts.push(Vertex::new( |
| 984 | path_cache.bounds.minx - fringe_width, |
| 985 | path_cache.bounds.maxy + fringe_width, |
| 986 | 0.5, |
| 987 | 1.0, |
| 988 | )); |
| 989 | self.verts.push(Vertex::new( |
| 990 | path_cache.bounds.minx - fringe_width, |
| 991 | path_cache.bounds.miny, |
| 992 | 0.5, |
| 993 | 1.0, |
| 994 | )); |
| 995 | |
| 996 | cmd.triangles_verts = Some((offset, 4)); |
| 997 | } |
| 998 | |
| 999 | self.append_cmd(cmd); |
| 1000 | } |
| 1001 | |
| 1002 | /// Strokes the provided Path with the specified Paint. |
| 1003 | pub fn stroke_path(&mut self, path: &Path, paint: &Paint) { |
| 1004 | self.stroke_path_internal(path, &paint.flavor, paint.shape_anti_alias, &paint.stroke); |
| 1005 | } |
| 1006 | |
| 1007 | fn stroke_path_internal( |
| 1008 | &mut self, |
| 1009 | path: &Path, |
| 1010 | paint_flavor: &PaintFlavor, |
| 1011 | anti_alias: bool, |
| 1012 | stroke: &StrokeSettings, |
| 1013 | ) { |
| 1014 | let mut paint_flavor = paint_flavor.clone(); |
| 1015 | let transform = self.state().transform; |
| 1016 | |
| 1017 | // The path cache saves a flattened and transformed version of the path. |
| 1018 | let mut path_cache = path.cache(&transform, self.tess_tol, self.dist_tol); |
| 1019 | |
| 1020 | // Early out if path is outside the canvas bounds |
| 1021 | if path_cache.bounds.maxx < 0.0 |
| 1022 | || path_cache.bounds.minx > self.width() as f32 |
| 1023 | || path_cache.bounds.maxy < 0.0 |
| 1024 | || path_cache.bounds.miny > self.height() as f32 |
| 1025 | { |
| 1026 | return; |
| 1027 | } |
| 1028 | |
| 1029 | let scissor = self.state().scissor; |
| 1030 | |
| 1031 | // Scale stroke width by current transform scale. |
| 1032 | // Note: I don't know why the original author clamped the max stroke width to 200, but it didn't |
| 1033 | // look correct when zooming in. There was probably a good reson for doing so and I may have |
| 1034 | // introduced a bug by removing the upper bound. |
| 1035 | //paint.set_stroke_width((paint.stroke_width() * transform.average_scale()).max(0.0).min(200.0)); |
| 1036 | let mut line_width = (stroke.line_width * transform.average_scale()).max(0.0); |
| 1037 | |
| 1038 | if line_width < self.fringe_width { |
| 1039 | // If the stroke width is less than pixel size, use alpha to emulate coverage. |
| 1040 | // Since coverage is area, scale by alpha*alpha. |
| 1041 | let alpha = (line_width / self.fringe_width).clamp(0.0, 1.0); |
| 1042 | |
| 1043 | paint_flavor.mul_alpha(alpha * alpha); |
| 1044 | line_width = self.fringe_width; |
| 1045 | } |
| 1046 | |
| 1047 | // Apply global alpha |
| 1048 | paint_flavor.mul_alpha(self.state().alpha); |
| 1049 | |
| 1050 | // Calculate stroke vertices. |
| 1051 | // expand_stroke will fill path_cache.contours[].stroke with vertex data for the GPU |
| 1052 | let fringe_with = if anti_alias { self.fringe_width } else { 0.0 }; |
| 1053 | path_cache.expand_stroke( |
| 1054 | line_width * 0.5, |
| 1055 | fringe_with, |
| 1056 | stroke.line_cap_start, |
| 1057 | stroke.line_cap_end, |
| 1058 | stroke.line_join, |
| 1059 | stroke.miter_limit, |
| 1060 | self.tess_tol, |
| 1061 | ); |
| 1062 | |
| 1063 | // GPU uniforms |
| 1064 | let params = Params::new( |
| 1065 | &self.images, |
| 1066 | &transform, |
| 1067 | &paint_flavor, |
| 1068 | &GlyphTexture::default(), |
| 1069 | &scissor, |
| 1070 | line_width, |
| 1071 | self.fringe_width, |
| 1072 | -1.0, |
| 1073 | ); |
| 1074 | |
| 1075 | let flavor = if stroke.stencil_strokes { |
| 1076 | let params2 = Params::new( |
| 1077 | &self.images, |
| 1078 | &transform, |
| 1079 | &paint_flavor, |
| 1080 | &GlyphTexture::default(), |
| 1081 | &scissor, |
| 1082 | line_width, |
| 1083 | self.fringe_width, |
| 1084 | 1.0 - 0.5 / 255.0, |
| 1085 | ); |
| 1086 | |
| 1087 | CommandType::StencilStroke { |
| 1088 | params1: params, |
| 1089 | params2, |
| 1090 | } |
| 1091 | } else { |
| 1092 | CommandType::Stroke { params } |
| 1093 | }; |
| 1094 | |
| 1095 | // GPU command |
| 1096 | let mut cmd = Command::new(flavor); |
| 1097 | cmd.composite_operation = self.state().composite_operation; |
| 1098 | |
| 1099 | if let PaintFlavor::Image { id, .. } = paint_flavor { |
| 1100 | cmd.image = Some(id); |
| 1101 | } else if let Some(paint::GradientColors::MultiStop { stops }) = paint_flavor.gradient_colors() { |
| 1102 | cmd.image = self |
| 1103 | .gradients |
| 1104 | .lookup_or_add(stops, &mut self.images, &mut self.renderer) |
| 1105 | .ok(); |
| 1106 | } |
| 1107 | |
| 1108 | // All verts from all shapes are kept in a single buffer here in the canvas. |
| 1109 | // Drawable struct is used to describe the range of vertices each draw call will operate on |
| 1110 | let mut offset = self.verts.len(); |
| 1111 | |
| 1112 | cmd.drawables.reserve_exact(path_cache.contours.len()); |
| 1113 | for contour in &path_cache.contours { |
| 1114 | let mut drawable = Drawable::default(); |
| 1115 | |
| 1116 | if !contour.stroke.is_empty() { |
| 1117 | drawable.stroke_verts = Some((offset, contour.stroke.len())); |
| 1118 | self.verts.extend_from_slice(&contour.stroke); |
| 1119 | offset += contour.stroke.len(); |
| 1120 | } |
| 1121 | |
| 1122 | cmd.drawables.push(drawable); |
| 1123 | } |
| 1124 | |
| 1125 | self.append_cmd(cmd); |
| 1126 | } |
| 1127 | |
| 1128 | fn render_unclipped_image_blit(&mut self, target_rect: &Rect, transform: &Transform2D, paint_flavor: &PaintFlavor) { |
| 1129 | let scissor = self.state().scissor; |
| 1130 | |
| 1131 | let mut params = Params::new( |
| 1132 | &self.images, |
| 1133 | transform, |
| 1134 | paint_flavor, |
| 1135 | &GlyphTexture::default(), |
| 1136 | &scissor, |
| 1137 | 0., |
| 1138 | 0., |
| 1139 | -1.0, |
| 1140 | ); |
| 1141 | params.shader_type = ShaderType::TextureCopyUnclipped; |
| 1142 | |
| 1143 | let mut cmd = Command::new(CommandType::Triangles { params }); |
| 1144 | cmd.composite_operation = self.state().composite_operation; |
| 1145 | |
| 1146 | let x0 = target_rect.x; |
| 1147 | let y0 = target_rect.y; |
| 1148 | let x1 = x0 + target_rect.w; |
| 1149 | let y1 = y0 + target_rect.h; |
| 1150 | |
| 1151 | let (p0, p1) = (x0, y0); |
| 1152 | let (p2, p3) = (x1, y0); |
| 1153 | let (p4, p5) = (x1, y1); |
| 1154 | let (p6, p7) = (x0, y1); |
| 1155 | |
| 1156 | // Apply the same mapping from vertex coordinates to texture coordinates as in the fragment shader, |
| 1157 | // but now ahead of time. |
| 1158 | let mut to_texture_space_transform = Transform2D::scaling(1. / params.extent[0], 1. / params.extent[1]); |
| 1159 | to_texture_space_transform.premultiply(&Transform2D([ |
| 1160 | params.paint_mat[0], |
| 1161 | params.paint_mat[1], |
| 1162 | params.paint_mat[4], |
| 1163 | params.paint_mat[5], |
| 1164 | params.paint_mat[8], |
| 1165 | params.paint_mat[9], |
| 1166 | ])); |
| 1167 | |
| 1168 | let (s0, t0) = to_texture_space_transform.transform_point(target_rect.x, target_rect.y); |
| 1169 | let (s1, t1) = |
| 1170 | to_texture_space_transform.transform_point(target_rect.x + target_rect.w, target_rect.y + target_rect.h); |
| 1171 | |
| 1172 | let verts = [ |
| 1173 | Vertex::new(p0, p1, s0, t0), |
| 1174 | Vertex::new(p4, p5, s1, t1), |
| 1175 | Vertex::new(p2, p3, s1, t0), |
| 1176 | Vertex::new(p0, p1, s0, t0), |
| 1177 | Vertex::new(p6, p7, s0, t1), |
| 1178 | Vertex::new(p4, p5, s1, t1), |
| 1179 | ]; |
| 1180 | |
| 1181 | if let &PaintFlavor::Image { id, .. } = paint_flavor { |
| 1182 | cmd.image = Some(id); |
| 1183 | } |
| 1184 | |
| 1185 | cmd.triangles_verts = Some((self.verts.len(), verts.len())); |
| 1186 | self.append_cmd(cmd); |
| 1187 | |
| 1188 | self.verts.extend_from_slice(&verts); |
| 1189 | } |
| 1190 | |
| 1191 | // Text |
| 1192 | |
| 1193 | /// Adds a font file to the canvas |
| 1194 | pub fn add_font<P: AsRef<FilePath>>(&mut self, file_path: P) -> Result<FontId, ErrorKind> { |
| 1195 | self.text_context.borrow_mut().add_font_file(file_path) |
| 1196 | } |
| 1197 | |
| 1198 | /// Adds a font to the canvas by reading it from the specified chunk of memory. |
| 1199 | pub fn add_font_mem(&mut self, data: &[u8]) -> Result<FontId, ErrorKind> { |
| 1200 | self.text_context.borrow_mut().add_font_mem(data) |
| 1201 | } |
| 1202 | |
| 1203 | /// Adds all .ttf files from a directory |
| 1204 | pub fn add_font_dir<P: AsRef<FilePath>>(&mut self, dir_path: P) -> Result<Vec<FontId>, ErrorKind> { |
| 1205 | self.text_context.borrow_mut().add_font_dir(dir_path) |
| 1206 | } |
| 1207 | |
| 1208 | /// Returns information on how the provided text will be drawn with the specified paint. |
| 1209 | pub fn measure_text<S: AsRef<str>>( |
| 1210 | &self, |
| 1211 | x: f32, |
| 1212 | y: f32, |
| 1213 | text: S, |
| 1214 | paint: &Paint, |
| 1215 | ) -> Result<TextMetrics, ErrorKind> { |
| 1216 | let scale = self.font_scale() * self.device_px_ratio; |
| 1217 | |
| 1218 | let mut text_settings = paint.text.clone(); |
| 1219 | text_settings.font_size *= scale; |
| 1220 | text_settings.letter_spacing *= scale; |
| 1221 | |
| 1222 | let scale = self.font_scale() * self.device_px_ratio; |
| 1223 | let invscale = 1.0 / scale; |
| 1224 | |
| 1225 | self.text_context |
| 1226 | .borrow_mut() |
| 1227 | .measure_text(x * scale, y * scale, text, &text_settings) |
| 1228 | .map(|mut metrics| { |
| 1229 | metrics.scale(invscale); |
| 1230 | metrics |
| 1231 | }) |
| 1232 | } |
| 1233 | |
| 1234 | /// Returns font metrics for a particular Paint. |
| 1235 | pub fn measure_font(&self, paint: &Paint) -> Result<FontMetrics, ErrorKind> { |
| 1236 | let scale = self.font_scale() * self.device_px_ratio; |
| 1237 | |
| 1238 | self.text_context |
| 1239 | .borrow_mut() |
| 1240 | .measure_font(paint.text.font_size * scale, &paint.text.font_ids) |
| 1241 | } |
| 1242 | |
| 1243 | /// Returns the maximum index-th byte of text that will fit inside `max_width`. |
| 1244 | /// |
| 1245 | /// The retuned index will always lie at the start and/or end of a UTF-8 code point sequence or at the start or end of the text |
| 1246 | pub fn break_text<S: AsRef<str>>(&self, max_width: f32, text: S, paint: &Paint) -> Result<usize, ErrorKind> { |
| 1247 | let scale = self.font_scale() * self.device_px_ratio; |
| 1248 | |
| 1249 | let mut text_settings = paint.text.clone(); |
| 1250 | text_settings.font_size *= scale; |
| 1251 | text_settings.letter_spacing *= scale; |
| 1252 | |
| 1253 | let max_width = max_width * scale; |
| 1254 | |
| 1255 | self.text_context |
| 1256 | .borrow_mut() |
| 1257 | .break_text(max_width, text, &text_settings) |
| 1258 | } |
| 1259 | |
| 1260 | /// Returnes a list of ranges representing each line of text that will fit inside `max_width` |
| 1261 | pub fn break_text_vec<S: AsRef<str>>( |
| 1262 | &self, |
| 1263 | max_width: f32, |
| 1264 | text: S, |
| 1265 | paint: &Paint, |
| 1266 | ) -> Result<Vec<Range<usize>>, ErrorKind> { |
| 1267 | let scale = self.font_scale() * self.device_px_ratio; |
| 1268 | |
| 1269 | let mut text_settings = paint.text.clone(); |
| 1270 | text_settings.font_size *= scale; |
| 1271 | text_settings.letter_spacing *= scale; |
| 1272 | |
| 1273 | let max_width = max_width * scale; |
| 1274 | |
| 1275 | self.text_context |
| 1276 | .borrow_mut() |
| 1277 | .break_text_vec(max_width, text, &text_settings) |
| 1278 | } |
| 1279 | |
| 1280 | /// Fills the provided string with the specified Paint. |
| 1281 | pub fn fill_text<S: AsRef<str>>( |
| 1282 | &mut self, |
| 1283 | x: f32, |
| 1284 | y: f32, |
| 1285 | text: S, |
| 1286 | paint: &Paint, |
| 1287 | ) -> Result<TextMetrics, ErrorKind> { |
| 1288 | self.draw_text(x, y, text.as_ref(), paint, RenderMode::Fill) |
| 1289 | } |
| 1290 | |
| 1291 | /// Strokes the provided string with the specified Paint. |
| 1292 | pub fn stroke_text<S: AsRef<str>>( |
| 1293 | &mut self, |
| 1294 | x: f32, |
| 1295 | y: f32, |
| 1296 | text: S, |
| 1297 | paint: &Paint, |
| 1298 | ) -> Result<TextMetrics, ErrorKind> { |
| 1299 | self.draw_text(x, y, text.as_ref(), paint, RenderMode::Stroke) |
| 1300 | } |
| 1301 | |
| 1302 | /// Dispatch an explicit set of `GlyphDrawCommands` to the renderer. Use this only if you are |
| 1303 | /// using a custom font rasterizer/layout. |
| 1304 | pub fn draw_glyph_commands(&mut self, draw_commands: GlyphDrawCommands, paint: &Paint, scale: f32) { |
| 1305 | let transform = self.state().transform; |
| 1306 | let invscale = 1.0 / scale; |
| 1307 | let create_vertices = |quads: &Vec<text::Quad>| { |
| 1308 | let mut verts = Vec::with_capacity(quads.len() * 6); |
| 1309 | |
| 1310 | for quad in quads { |
| 1311 | let left = quad.x0 * invscale; |
| 1312 | let right = quad.x1 * invscale; |
| 1313 | let top = quad.y0 * invscale; |
| 1314 | let bottom = quad.y1 * invscale; |
| 1315 | |
| 1316 | let (p0, p1) = transform.transform_point(left, top); |
| 1317 | let (p2, p3) = transform.transform_point(right, top); |
| 1318 | let (p4, p5) = transform.transform_point(right, bottom); |
| 1319 | let (p6, p7) = transform.transform_point(left, bottom); |
| 1320 | |
| 1321 | verts.push(Vertex::new(p0, p1, quad.s0, quad.t0)); |
| 1322 | verts.push(Vertex::new(p4, p5, quad.s1, quad.t1)); |
| 1323 | verts.push(Vertex::new(p2, p3, quad.s1, quad.t0)); |
| 1324 | verts.push(Vertex::new(p0, p1, quad.s0, quad.t0)); |
| 1325 | verts.push(Vertex::new(p6, p7, quad.s0, quad.t1)); |
| 1326 | verts.push(Vertex::new(p4, p5, quad.s1, quad.t1)); |
| 1327 | } |
| 1328 | verts |
| 1329 | }; |
| 1330 | |
| 1331 | // Apply global alpha |
| 1332 | let mut paint_flavor = paint.flavor.clone(); |
| 1333 | paint_flavor.mul_alpha(self.state().alpha); |
| 1334 | |
| 1335 | for cmd in draw_commands.alpha_glyphs { |
| 1336 | let verts = create_vertices(&cmd.quads); |
| 1337 | |
| 1338 | self.render_triangles(&verts, &transform, &paint_flavor, GlyphTexture::AlphaMask(cmd.image_id)); |
| 1339 | } |
| 1340 | |
| 1341 | for cmd in draw_commands.color_glyphs { |
| 1342 | let verts = create_vertices(&cmd.quads); |
| 1343 | |
| 1344 | self.render_triangles( |
| 1345 | &verts, |
| 1346 | &transform, |
| 1347 | &paint_flavor, |
| 1348 | GlyphTexture::ColorTexture(cmd.image_id), |
| 1349 | ); |
| 1350 | } |
| 1351 | } |
| 1352 | |
| 1353 | // Private |
| 1354 | |
| 1355 | fn draw_text( |
| 1356 | &mut self, |
| 1357 | x: f32, |
| 1358 | y: f32, |
| 1359 | text: &str, |
| 1360 | paint: &Paint, |
| 1361 | render_mode: RenderMode, |
| 1362 | ) -> Result<TextMetrics, ErrorKind> { |
| 1363 | let scale = self.font_scale() * self.device_px_ratio; |
| 1364 | let invscale = 1.0 / scale; |
| 1365 | |
| 1366 | let mut stroke = paint.stroke.clone(); |
| 1367 | stroke.line_width *= scale; |
| 1368 | |
| 1369 | let mut text_settings = paint.text.clone(); |
| 1370 | text_settings.font_size *= scale; |
| 1371 | text_settings.letter_spacing *= scale; |
| 1372 | |
| 1373 | let mut layout = text::shape( |
| 1374 | x * scale, |
| 1375 | y * scale, |
| 1376 | &mut self.text_context.borrow_mut(), |
| 1377 | &text_settings, |
| 1378 | text, |
| 1379 | None, |
| 1380 | )?; |
| 1381 | //let layout = self.layout_text(x, y, text, &paint)?; |
| 1382 | |
| 1383 | // TODO: Early out if text is outside the canvas bounds, or maybe even check for each character in layout. |
| 1384 | |
| 1385 | let bitmap_glyphs = layout.has_bitmap_glyphs(); |
| 1386 | let need_direct_rendering = text_settings.font_size > 92.0; |
| 1387 | |
| 1388 | if need_direct_rendering && !bitmap_glyphs { |
| 1389 | text::render_direct( |
| 1390 | self, |
| 1391 | &layout, |
| 1392 | &paint.flavor, |
| 1393 | paint.shape_anti_alias, |
| 1394 | &stroke, |
| 1395 | text_settings.font_size, |
| 1396 | render_mode, |
| 1397 | invscale, |
| 1398 | )?; |
| 1399 | } else { |
| 1400 | let atlas = if bitmap_glyphs && need_direct_rendering { |
| 1401 | self.ephemeral_glyph_atlas.get_or_insert_with(Default::default).clone() |
| 1402 | } else { |
| 1403 | self.glyph_atlas.clone() |
| 1404 | }; |
| 1405 | |
| 1406 | let draw_commands = |
| 1407 | atlas.render_atlas(self, &layout, text_settings.font_size, stroke.line_width, render_mode)?; |
| 1408 | self.draw_glyph_commands(draw_commands, paint, scale); |
| 1409 | } |
| 1410 | |
| 1411 | layout.scale(invscale); |
| 1412 | |
| 1413 | Ok(layout) |
| 1414 | } |
| 1415 | |
| 1416 | fn render_triangles( |
| 1417 | &mut self, |
| 1418 | verts: &[Vertex], |
| 1419 | transform: &Transform2D, |
| 1420 | paint_flavor: &PaintFlavor, |
| 1421 | glyph_texture: GlyphTexture, |
| 1422 | ) { |
| 1423 | let scissor = self.state().scissor; |
| 1424 | |
| 1425 | let params = Params::new( |
| 1426 | &self.images, |
| 1427 | transform, |
| 1428 | paint_flavor, |
| 1429 | &glyph_texture, |
| 1430 | &scissor, |
| 1431 | 1.0, |
| 1432 | 1.0, |
| 1433 | -1.0, |
| 1434 | ); |
| 1435 | |
| 1436 | let mut cmd = Command::new(CommandType::Triangles { params }); |
| 1437 | cmd.composite_operation = self.state().composite_operation; |
| 1438 | cmd.glyph_texture = glyph_texture; |
| 1439 | |
| 1440 | if let &PaintFlavor::Image { id, .. } = paint_flavor { |
| 1441 | cmd.image = Some(id); |
| 1442 | } else if let Some(paint::GradientColors::MultiStop { stops }) = paint_flavor.gradient_colors() { |
| 1443 | cmd.image = self |
| 1444 | .gradients |
| 1445 | .lookup_or_add(stops, &mut self.images, &mut self.renderer) |
| 1446 | .ok(); |
| 1447 | } |
| 1448 | |
| 1449 | cmd.triangles_verts = Some((self.verts.len(), verts.len())); |
| 1450 | self.append_cmd(cmd); |
| 1451 | |
| 1452 | self.verts.extend_from_slice(verts); |
| 1453 | } |
| 1454 | |
| 1455 | fn font_scale(&self) -> f32 { |
| 1456 | let avg_scale = self.state().transform.average_scale(); |
| 1457 | |
| 1458 | geometry::quantize(avg_scale, 0.1).min(7.0) |
| 1459 | } |
| 1460 | |
| 1461 | // |
| 1462 | |
| 1463 | fn state(&self) -> &State { |
| 1464 | self.state_stack.last().unwrap() |
| 1465 | } |
| 1466 | |
| 1467 | fn state_mut(&mut self) -> &mut State { |
| 1468 | self.state_stack.last_mut().unwrap() |
| 1469 | } |
| 1470 | |
| 1471 | /// Get a list of all font textures. |
| 1472 | #[cfg (feature = "debug_inspector" )] |
| 1473 | pub fn debug_inspector_get_font_textures(&self) -> Vec<ImageId> { |
| 1474 | self.glyph_atlas |
| 1475 | .glyph_textures |
| 1476 | .borrow() |
| 1477 | .iter() |
| 1478 | .map(|t| t.image_id) |
| 1479 | .collect() |
| 1480 | } |
| 1481 | |
| 1482 | /// Draws an image with the specified `id` on the whole canvas. |
| 1483 | #[cfg (feature = "debug_inspector" )] |
| 1484 | pub fn debug_inspector_draw_image(&mut self, id: ImageId) { |
| 1485 | if let Ok(size) = self.image_size(id) { |
| 1486 | let width = size.0 as f32; |
| 1487 | let height = size.1 as f32; |
| 1488 | let mut path = Path::new(); |
| 1489 | path.rect(0f32, 0f32, width, height); |
| 1490 | self.fill_path(&path, &Paint::image(id, 0f32, 0f32, width, height, 0f32, 1f32)); |
| 1491 | } |
| 1492 | } |
| 1493 | } |
| 1494 | |
| 1495 | impl<T> Canvas<T> |
| 1496 | where |
| 1497 | T: SurfacelessRenderer, |
| 1498 | { |
| 1499 | /// Tells the renderer to execute all drawing commands and clears the current internal state |
| 1500 | /// |
| 1501 | /// Call this at the end of each frame. |
| 1502 | pub fn flush(&mut self) { |
| 1503 | self.renderer |
| 1504 | .render_surfaceless(&mut self.images, &self.verts, commands:std::mem::take(&mut self.commands)); |
| 1505 | self.verts.clear(); |
| 1506 | self.gradients |
| 1507 | .release_old_gradients(&mut self.images, &mut self.renderer); |
| 1508 | if let Some(atlas: Rc) = self.ephemeral_glyph_atlas.take() { |
| 1509 | atlas.clear(self); |
| 1510 | } |
| 1511 | } |
| 1512 | } |
| 1513 | |
| 1514 | impl<T: Renderer> Drop for Canvas<T> { |
| 1515 | fn drop(&mut self) { |
| 1516 | self.images.clear(&mut self.renderer); |
| 1517 | } |
| 1518 | } |
| 1519 | |
| 1520 | // re-exports |
| 1521 | #[cfg (feature = "image-loading" )] |
| 1522 | pub use ::image as img; |
| 1523 | |
| 1524 | pub use imgref; |
| 1525 | pub use rgb; |
| 1526 | |
| 1527 | /// Internal structure that implements the Renderer trait for unit testing. |
| 1528 | #[cfg (test)] |
| 1529 | #[derive (Default)] |
| 1530 | pub struct RecordingRenderer { |
| 1531 | /// Vector of the last commands submitted to the renderer. |
| 1532 | pub last_commands: Rc<RefCell<Vec<renderer::Command>>>, |
| 1533 | } |
| 1534 | |
| 1535 | #[cfg (test)] |
| 1536 | impl Renderer for RecordingRenderer { |
| 1537 | type Image = DummyImage; |
| 1538 | type NativeTexture = (); |
| 1539 | type Surface = (); |
| 1540 | type CommandBuffer = (); |
| 1541 | |
| 1542 | fn set_size(&mut self, _width: u32, _height: u32, _dpi: f32) {} |
| 1543 | |
| 1544 | fn render( |
| 1545 | &mut self, |
| 1546 | _surface: &Self::Surface, |
| 1547 | _images: &mut ImageStore<Self::Image>, |
| 1548 | _verts: &[renderer::Vertex], |
| 1549 | commands: Vec<renderer::Command>, |
| 1550 | ) { |
| 1551 | *self.last_commands.borrow_mut() = commands; |
| 1552 | } |
| 1553 | |
| 1554 | fn alloc_image(&mut self, info: crate::ImageInfo) -> Result<Self::Image, ErrorKind> { |
| 1555 | Ok(Self::Image { info }) |
| 1556 | } |
| 1557 | |
| 1558 | fn create_image_from_native_texture( |
| 1559 | &mut self, |
| 1560 | _native_texture: Self::NativeTexture, |
| 1561 | _info: crate::ImageInfo, |
| 1562 | ) -> Result<Self::Image, ErrorKind> { |
| 1563 | Err(ErrorKind::UnsupportedImageFormat) |
| 1564 | } |
| 1565 | |
| 1566 | fn update_image( |
| 1567 | &mut self, |
| 1568 | image: &mut Self::Image, |
| 1569 | data: crate::ImageSource, |
| 1570 | x: usize, |
| 1571 | y: usize, |
| 1572 | ) -> Result<(), ErrorKind> { |
| 1573 | let size = data.dimensions(); |
| 1574 | |
| 1575 | if x + size.width > image.info.width() { |
| 1576 | return Err(ErrorKind::ImageUpdateOutOfBounds); |
| 1577 | } |
| 1578 | |
| 1579 | if y + size.height > image.info.height() { |
| 1580 | return Err(ErrorKind::ImageUpdateOutOfBounds); |
| 1581 | } |
| 1582 | |
| 1583 | Ok(()) |
| 1584 | } |
| 1585 | |
| 1586 | fn delete_image(&mut self, _image: Self::Image, _image_id: crate::ImageId) {} |
| 1587 | |
| 1588 | fn screenshot(&mut self) -> Result<imgref::ImgVec<rgb::RGBA8>, ErrorKind> { |
| 1589 | Ok(imgref::ImgVec::new(Vec::new(), 0, 0)) |
| 1590 | } |
| 1591 | } |
| 1592 | |
| 1593 | /// Dummy image type used for tests. |
| 1594 | #[cfg (test)] |
| 1595 | pub struct DummyImage { |
| 1596 | info: ImageInfo, |
| 1597 | } |
| 1598 | |
| 1599 | #[test ] |
| 1600 | fn test_image_blit_fast_path() { |
| 1601 | use renderer::{Command, CommandType}; |
| 1602 | |
| 1603 | let renderer = RecordingRenderer::default(); |
| 1604 | let recorded_commands = renderer.last_commands.clone(); |
| 1605 | let mut canvas = Canvas::new(renderer).unwrap(); |
| 1606 | canvas.set_size(100, 100, 1.); |
| 1607 | let mut path = Path::new(); |
| 1608 | path.rect(10., 10., 50., 50.); |
| 1609 | let image = canvas |
| 1610 | .create_image_empty(30, 30, PixelFormat::Rgba8, ImageFlags::empty()) |
| 1611 | .unwrap(); |
| 1612 | let paint = Paint::image(image, 0., 0., 30., 30., 0., 0.).with_anti_alias(false); |
| 1613 | canvas.fill_path(&path, &paint); |
| 1614 | canvas.flush_to_surface(&()); |
| 1615 | |
| 1616 | let commands = recorded_commands.borrow(); |
| 1617 | let mut commands = commands.iter(); |
| 1618 | assert!(matches!( |
| 1619 | commands.next(), |
| 1620 | Some(Command { |
| 1621 | cmd_type: CommandType::SetRenderTarget(..), |
| 1622 | .. |
| 1623 | }) |
| 1624 | )); |
| 1625 | assert!(matches!( |
| 1626 | commands.next(), |
| 1627 | Some(Command { |
| 1628 | cmd_type: CommandType::Triangles { |
| 1629 | params: Params { |
| 1630 | shader_type: renderer::ShaderType::TextureCopyUnclipped, |
| 1631 | .. |
| 1632 | } |
| 1633 | }, |
| 1634 | .. |
| 1635 | }) |
| 1636 | )); |
| 1637 | } |
| 1638 | |