├── .gitignore ├── .vscode └── launch.json ├── Cargo.toml ├── LICENSE ├── README.md ├── layout.kdl ├── points_on_curve ├── Cargo.toml ├── LICENSE ├── README.md ├── assets │ └── tolerance.png ├── examples │ └── tolerance.rs └── src │ └── lib.rs ├── rough_piet ├── Cargo.toml ├── README.md ├── examples │ ├── arc.rs │ ├── bezier_cubic.rs │ ├── bezier_quadratic.rs │ ├── circle.rs │ ├── curve.rs │ ├── curve_fill.rs │ ├── ellipse.rs │ ├── heart_svg_path.rs │ ├── linear_path.rs │ ├── polygon.rs │ ├── rectangle.rs │ ├── rotate.rs │ ├── rust_logo.rs │ ├── rust_logo_alpha.rs │ ├── scale.rs │ ├── skew.rs │ ├── skewy.rs │ ├── stroke_style.rs │ ├── text.rs │ └── translate.rs └── src │ ├── kurbo_generator.rs │ └── lib.rs ├── rough_tiny_skia ├── Cargo.toml ├── examples │ ├── rectangle.rs │ └── rectangle_alpha.rs └── src │ ├── lib.rs │ └── skia_generator.rs ├── roughr ├── .gitignore ├── Cargo.toml ├── README.md ├── assets │ ├── circle.png │ ├── ellipse.png │ ├── heart_svg_path.png │ ├── rectangle.png │ ├── rough_text.png │ └── rust.png └── src │ ├── core.rs │ ├── filler │ ├── dashed_filler.rs │ ├── dot_filler.rs │ ├── hatch_filler.rs │ ├── mod.rs │ ├── scan_line_hachure.rs │ ├── traits.rs │ ├── zig_zag_filler.rs │ └── zig_zag_line_filler.rs │ ├── generator.rs │ ├── geometry.rs │ ├── lib.rs │ ├── points_on_path.rs │ └── renderer.rs ├── rustfmt.toml └── svg_path_ops ├── Cargo.toml ├── LICENSE ├── README.md ├── assets ├── rotated_cat.png ├── scaled_cat.png ├── skewed_cat.png └── translated_cat.png └── src ├── a2c.rs ├── bbox.rs ├── ellipse.rs ├── lib.rs └── pt.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled files and executables 2 | debug/ 3 | target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | # MSVC Windows builds of rustc generate these, which store debugging information 13 | *.pdb -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "lldb", 9 | "request": "launch", 10 | "name": "Debug unit tests in library 'roughr'", 11 | "cargo": { 12 | "args": [ 13 | "test", 14 | "--no-run", 15 | "--lib", 16 | "--package=roughr" 17 | ], 18 | "filter": { 19 | "name": "roughr", 20 | "kind": "lib" 21 | } 22 | }, 23 | "args": [], 24 | "cwd": "${workspaceFolder}" 25 | }, 26 | { 27 | "type": "lldb", 28 | "request": "launch", 29 | "name": "Debug unit tests in library 'points_on_curve'", 30 | "cargo": { 31 | "args": [ 32 | "test", 33 | "--no-run", 34 | "--lib", 35 | "--package=points_on_curve" 36 | ], 37 | "filter": { 38 | "name": "points_on_curve", 39 | "kind": "lib" 40 | } 41 | }, 42 | "args": [], 43 | "cwd": "${workspaceFolder}" 44 | }, 45 | { 46 | "type": "lldb", 47 | "request": "launch", 48 | "name": "Debug example 'tolerance'", 49 | "cargo": { 50 | "args": [ 51 | "build", 52 | "--example=tolerance", 53 | "--package=points_on_curve" 54 | ], 55 | "filter": { 56 | "name": "tolerance", 57 | "kind": "example" 58 | } 59 | }, 60 | "args": [], 61 | "cwd": "${workspaceFolder}" 62 | }, 63 | { 64 | "type": "lldb", 65 | "request": "launch", 66 | "name": "Debug unit tests in example 'tolerance'", 67 | "cargo": { 68 | "args": [ 69 | "test", 70 | "--no-run", 71 | "--example=tolerance", 72 | "--package=points_on_curve" 73 | ], 74 | "filter": { 75 | "name": "tolerance", 76 | "kind": "example" 77 | } 78 | }, 79 | "args": [], 80 | "cwd": "${workspaceFolder}" 81 | }, 82 | { 83 | "type": "lldb", 84 | "request": "launch", 85 | "name": "Debug unit tests in library 'svg_path_ops'", 86 | "cargo": { 87 | "args": [ 88 | "test", 89 | "--no-run", 90 | "--lib", 91 | "--package=svg_path_ops" 92 | ], 93 | "filter": { 94 | "name": "svg_path_ops", 95 | "kind": "lib" 96 | } 97 | }, 98 | "args": [], 99 | "cwd": "${workspaceFolder}" 100 | }, 101 | { 102 | "type": "lldb", 103 | "request": "launch", 104 | "name": "Debug unit tests in library 'rough_piet'", 105 | "cargo": { 106 | "args": [ 107 | "test", 108 | "--no-run", 109 | "--lib", 110 | "--package=rough_piet" 111 | ], 112 | "filter": { 113 | "name": "rough_piet", 114 | "kind": "lib" 115 | } 116 | }, 117 | "args": [], 118 | "cwd": "${workspaceFolder}" 119 | }, 120 | { 121 | "type": "lldb", 122 | "request": "launch", 123 | "name": "Debug example 'linear_path'", 124 | "cargo": { 125 | "args": [ 126 | "build", 127 | "--example=linear_path", 128 | "--package=rough_piet" 129 | ], 130 | "filter": { 131 | "name": "linear_path", 132 | "kind": "example" 133 | } 134 | }, 135 | "args": [], 136 | "cwd": "${workspaceFolder}" 137 | }, 138 | { 139 | "type": "lldb", 140 | "request": "launch", 141 | "name": "Debug unit tests in example 'linear_path'", 142 | "cargo": { 143 | "args": [ 144 | "test", 145 | "--no-run", 146 | "--example=linear_path", 147 | "--package=rough_piet" 148 | ], 149 | "filter": { 150 | "name": "linear_path", 151 | "kind": "example" 152 | } 153 | }, 154 | "args": [], 155 | "cwd": "${workspaceFolder}" 156 | }, 157 | { 158 | "type": "lldb", 159 | "request": "launch", 160 | "name": "Debug example 'rust_logo'", 161 | "cargo": { 162 | "args": [ 163 | "build", 164 | "--example=rust_logo", 165 | "--package=rough_piet" 166 | ], 167 | "filter": { 168 | "name": "rust_logo", 169 | "kind": "example" 170 | } 171 | }, 172 | "args": [], 173 | "cwd": "${workspaceFolder}" 174 | }, 175 | { 176 | "type": "lldb", 177 | "request": "launch", 178 | "name": "Debug unit tests in example 'rust_logo'", 179 | "cargo": { 180 | "args": [ 181 | "test", 182 | "--no-run", 183 | "--example=rust_logo", 184 | "--package=rough_piet" 185 | ], 186 | "filter": { 187 | "name": "rust_logo", 188 | "kind": "example" 189 | } 190 | }, 191 | "args": [], 192 | "cwd": "${workspaceFolder}" 193 | }, 194 | { 195 | "type": "lldb", 196 | "request": "launch", 197 | "name": "Debug example 'rectangle'", 198 | "cargo": { 199 | "args": [ 200 | "build", 201 | "--example=rectangle", 202 | "--package=rough_piet" 203 | ], 204 | "filter": { 205 | "name": "rectangle", 206 | "kind": "example" 207 | } 208 | }, 209 | "args": [], 210 | "cwd": "${workspaceFolder}" 211 | }, 212 | { 213 | "type": "lldb", 214 | "request": "launch", 215 | "name": "Debug unit tests in example 'rectangle'", 216 | "cargo": { 217 | "args": [ 218 | "test", 219 | "--no-run", 220 | "--example=rectangle", 221 | "--package=rough_piet" 222 | ], 223 | "filter": { 224 | "name": "rectangle", 225 | "kind": "example" 226 | } 227 | }, 228 | "args": [], 229 | "cwd": "${workspaceFolder}" 230 | }, 231 | { 232 | "type": "lldb", 233 | "request": "launch", 234 | "name": "Debug example 'ellipse'", 235 | "cargo": { 236 | "args": [ 237 | "build", 238 | "--example=ellipse", 239 | "--package=rough_piet" 240 | ], 241 | "filter": { 242 | "name": "ellipse", 243 | "kind": "example" 244 | } 245 | }, 246 | "args": [], 247 | "cwd": "${workspaceFolder}" 248 | }, 249 | { 250 | "type": "lldb", 251 | "request": "launch", 252 | "name": "Debug unit tests in example 'ellipse'", 253 | "cargo": { 254 | "args": [ 255 | "test", 256 | "--no-run", 257 | "--example=ellipse", 258 | "--package=rough_piet" 259 | ], 260 | "filter": { 261 | "name": "ellipse", 262 | "kind": "example" 263 | } 264 | }, 265 | "args": [], 266 | "cwd": "${workspaceFolder}" 267 | }, 268 | { 269 | "type": "lldb", 270 | "request": "launch", 271 | "name": "Debug example 'polygon'", 272 | "cargo": { 273 | "args": [ 274 | "build", 275 | "--example=polygon", 276 | "--package=rough_piet" 277 | ], 278 | "filter": { 279 | "name": "polygon", 280 | "kind": "example" 281 | } 282 | }, 283 | "args": [], 284 | "cwd": "${workspaceFolder}" 285 | }, 286 | { 287 | "type": "lldb", 288 | "request": "launch", 289 | "name": "Debug unit tests in example 'polygon'", 290 | "cargo": { 291 | "args": [ 292 | "test", 293 | "--no-run", 294 | "--example=polygon", 295 | "--package=rough_piet" 296 | ], 297 | "filter": { 298 | "name": "polygon", 299 | "kind": "example" 300 | } 301 | }, 302 | "args": [], 303 | "cwd": "${workspaceFolder}" 304 | }, 305 | { 306 | "type": "lldb", 307 | "request": "launch", 308 | "name": "Debug example 'circle'", 309 | "cargo": { 310 | "args": [ 311 | "build", 312 | "--example=circle", 313 | "--package=rough_piet" 314 | ], 315 | "filter": { 316 | "name": "circle", 317 | "kind": "example" 318 | } 319 | }, 320 | "args": [], 321 | "cwd": "${workspaceFolder}" 322 | }, 323 | { 324 | "type": "lldb", 325 | "request": "launch", 326 | "name": "Debug unit tests in example 'circle'", 327 | "cargo": { 328 | "args": [ 329 | "test", 330 | "--no-run", 331 | "--example=circle", 332 | "--package=rough_piet" 333 | ], 334 | "filter": { 335 | "name": "circle", 336 | "kind": "example" 337 | } 338 | }, 339 | "args": [], 340 | "cwd": "${workspaceFolder}" 341 | }, 342 | { 343 | "type": "lldb", 344 | "request": "launch", 345 | "name": "Debug example 'arc'", 346 | "cargo": { 347 | "args": [ 348 | "build", 349 | "--example=arc", 350 | "--package=rough_piet" 351 | ], 352 | "filter": { 353 | "name": "arc", 354 | "kind": "example" 355 | } 356 | }, 357 | "args": [], 358 | "cwd": "${workspaceFolder}" 359 | }, 360 | { 361 | "type": "lldb", 362 | "request": "launch", 363 | "name": "Debug unit tests in example 'arc'", 364 | "cargo": { 365 | "args": [ 366 | "test", 367 | "--no-run", 368 | "--example=arc", 369 | "--package=rough_piet" 370 | ], 371 | "filter": { 372 | "name": "arc", 373 | "kind": "example" 374 | } 375 | }, 376 | "args": [], 377 | "cwd": "${workspaceFolder}" 378 | }, 379 | { 380 | "type": "lldb", 381 | "request": "launch", 382 | "name": "Debug example 'heart_svg_path'", 383 | "cargo": { 384 | "args": [ 385 | "build", 386 | "--example=heart_svg_path", 387 | "--package=rough_piet" 388 | ], 389 | "filter": { 390 | "name": "heart_svg_path", 391 | "kind": "example" 392 | } 393 | }, 394 | "args": [], 395 | "cwd": "${workspaceFolder}" 396 | }, 397 | { 398 | "type": "lldb", 399 | "request": "launch", 400 | "name": "Debug unit tests in example 'heart_svg_path'", 401 | "cargo": { 402 | "args": [ 403 | "test", 404 | "--no-run", 405 | "--example=heart_svg_path", 406 | "--package=rough_piet" 407 | ], 408 | "filter": { 409 | "name": "heart_svg_path", 410 | "kind": "example" 411 | } 412 | }, 413 | "args": [], 414 | "cwd": "${workspaceFolder}" 415 | }, 416 | { 417 | "type": "lldb", 418 | "request": "launch", 419 | "name": "Debug example 'curve'", 420 | "cargo": { 421 | "args": [ 422 | "build", 423 | "--example=curve", 424 | "--package=rough_piet" 425 | ], 426 | "filter": { 427 | "name": "curve", 428 | "kind": "example" 429 | } 430 | }, 431 | "args": [], 432 | "cwd": "${workspaceFolder}" 433 | }, 434 | { 435 | "type": "lldb", 436 | "request": "launch", 437 | "name": "Debug unit tests in example 'curve'", 438 | "cargo": { 439 | "args": [ 440 | "test", 441 | "--no-run", 442 | "--example=curve", 443 | "--package=rough_piet" 444 | ], 445 | "filter": { 446 | "name": "curve", 447 | "kind": "example" 448 | } 449 | }, 450 | "args": [], 451 | "cwd": "${workspaceFolder}" 452 | } 453 | ] 454 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "roughr", 4 | "rough_piet", 5 | "points_on_curve", 6 | "svg_path_ops", 7 | "rough_tiny_skia", 8 | ] 9 | resolver = "2" 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Orhan Balci 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rough-rs 2 | 3 | ![rustroughlogo](https://github.com/orhanbalci/rough-rs/blob/main/roughr/assets/rust.png?raw=true) 4 | 5 | ![roughtext](https://github.com/orhanbalci/rough-rs/blob/main/roughr/assets/rough_text.png?raw=true) 6 | 7 | This repository contains a set of crates which in result resembles functionality in [Rough.js](https://github.com/rough-stuff/rough) 8 | 9 | - [points_on_curve](https://github.com/orhanbalci/rough-rs/tree/main/points_on_curve) rustlang port of [points-on-curve](https://github.com/pshihn/bezier-points) npm package written by 10 | [@pshihn](https://github.com/pshihn). 11 | 12 | - [svg_path_ops](https://github.com/orhanbalci/rough-rs/tree/main/svg_path_ops) originates from [path-data-parser](https://github.com/pshihn/path-data-parser) but not limited to this 13 | packages functionality 14 | 15 | - [roughr](https://github.com/orhanbalci/rough-rs/tree/main/roughr) core implementation of [Rough.js](https://github.com/rough-stuff/rough) drawing primitives 16 | 17 | - [rough_piet](https://github.com/orhanbalci/rough-rs/tree/main/rough_piet) adapter between [roughr](https://github.com/orhanbalci/rough-rs/tree/main/roughr) and [piet](https://github.com/linebender/piet) 18 | 19 | - [rough_tiny_skia](https://github.com/orhanbalci/rough-rs/tree/main/rough_tiny_skia) adapter between [roughr](https://github.com/orhanbalci/rough-rs/tree/main/roughr) and [tiny-skia](https://github.com/RazrFalcon/tiny-skia) 20 | 21 | ## 📝 License 22 | 23 | Licensed under MIT License ([LICENSE](LICENSE)). 24 | 25 | ### 🚧 Contributions 26 | 27 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the MIT license, shall be licensed as above, without any additional terms or conditions. 28 | -------------------------------------------------------------------------------- /layout.kdl: -------------------------------------------------------------------------------- 1 | layout { 2 | pane size=1 borderless=true { 3 | plugin location="zellij:tab-bar" 4 | } 5 | pane split_direction="vertical" { 6 | pane edit="roughr/src/lib.rs" size="65%" 7 | pane split_direction="horizontal"{ 8 | pane command="cargo" { 9 | args "test" 10 | } 11 | pane 12 | } 13 | } 14 | pane size=2 borderless=true { 15 | plugin location="zellij:status-bar" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /points_on_curve/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "points_on_curve" 3 | version = "0.7.0" 4 | edition = "2021" 5 | authors = ["orhanbalci@gmail.com "] 6 | description = "Points on Bezier Curves" 7 | repository = "https://github.com/orhanbalci/rough-rs.git" 8 | homepage = "https://github.com/orhanbalci" 9 | keywords = ["graphics", "bezier"] 10 | categories = ["graphics"] 11 | license = "MIT" 12 | readme = "README.md" 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | 16 | [dependencies] 17 | euclid = "0.22" 18 | num-traits = "0.2" 19 | 20 | [dev-dependencies] 21 | piet-common = {version = "0.6", features = ["png"]} 22 | piet = "0.6" 23 | -------------------------------------------------------------------------------- /points_on_curve/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Orhan Balci 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /points_on_curve/README.md: -------------------------------------------------------------------------------- 1 | # points_on_curve 〰️ 📌 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/points_on_curve.svg)](https://crates.io/crates/points_on_curve) 4 | [![Documentation](https://docs.rs/points_on_curve/badge.svg)](https://docs.rs/points_on_curve) 5 | [![License](https://img.shields.io/github/license/orhanbalci/rough-rs.svg)](https://github.com/orhanbalci/rough-rs/blob/main/points_on_curve/LICENSE) 6 | 7 | 8 | 9 | 10 | This crate is a rustlang port of [points-on-curve](https://github.com/pshihn/bezier-points) npm package written by 11 | [@pshihn](https://github.com/pshihn). 12 | 13 | This package exposes functions to sample points on a bezier curve with certain tolerance. 14 | There is also a utility funtion to simplify the shape to use fewer points. 15 | This can really be useful when estimating lines/polygons for curves in WebGL or for Hit/Collision detections. 16 | Reverse of this operation is also supported meaning given some points generate bezier curve points passing through this points 17 | 18 | 19 | ## 📦 Cargo.toml 20 | 21 | ```toml 22 | [dependencies] 23 | points_on_curve = "0.1" 24 | ``` 25 | 26 | ## 🔧 Example 27 | 28 | ```rust 29 | use euclid::{default, point2}; 30 | use points_on_curve::points_on_bezier_curves; 31 | 32 | let input = vec![ 33 | point2(70.0, 240.0), 34 | point2(145.0, 60.0), 35 | point2(275.0, 90.0), 36 | point2(300.0, 230.0), 37 | ]; 38 | let result_015 = points_on_bezier_curves(&input, 0.2, Some(0.15)); 39 | 40 | ``` 41 | 42 | 43 | ## 🖨️ Output 44 | This picture shows computed points with 4 different distance values 0.15, 0.75, 1.5 and 3.0 with tolerance 2.0. 45 | 46 | ![tolerance](https://raw.githubusercontent.com/orhanbalci/rough-rs/main/points_on_curve/assets/tolerance.png) 47 | 48 | 49 | ## 🔭 Examples 50 | 51 | For more examples have a look at the 52 | [examples](https://github.com/orhanbalci/rough-rs/blob/main/points_on_curve/examples) folder. 53 | 54 | 55 | 56 | 57 | 58 | 59 | ## 📝 License 60 | 61 | Licensed under MIT License ([LICENSE](LICENSE)). 62 | 63 | ### 🚧 Contributions 64 | 65 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the MIT license, shall be licensed as above, without any additional terms or conditions. 66 | -------------------------------------------------------------------------------- /points_on_curve/assets/tolerance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhanbalci/rough-rs/9449e874ef680fdf9afd6d04ca6fe69c0df9862d/points_on_curve/assets/tolerance.png -------------------------------------------------------------------------------- /points_on_curve/examples/tolerance.rs: -------------------------------------------------------------------------------- 1 | //! This example plots bezier curve and computed points on it 2 | 3 | use euclid::{default, point2}; 4 | use piet::kurbo::{Circle, CubicBez, Point, TranslateScale, Vec2}; 5 | use piet::{Color, RenderContext}; 6 | use piet_common::kurbo::Rect; 7 | use piet_common::{CairoRenderContext, Device}; 8 | use points_on_curve::points_on_bezier_curves; 9 | 10 | const WIDTH: usize = 740; 11 | const HEIGHT: usize = 500; 12 | /// For now, assume pixel density (dots per inch) 13 | const DPI: f64 = 96.; 14 | 15 | /// Feature "png" needed for save_to_file() and it's disabled by default for optional dependencies 16 | /// cargo run --example mondrian --features png 17 | fn main() { 18 | let mut device = Device::new().unwrap(); 19 | let mut bitmap = device.bitmap_target(WIDTH, HEIGHT, 1.0).unwrap(); 20 | let mut rc = bitmap.render_context(); 21 | 22 | let background_color = Color::from_hex_str("96C0B7").unwrap(); 23 | let stroke_color = Color::from_hex_str("725752").unwrap(); 24 | let sketch_color = Color::from_hex_str("FEF6C9").unwrap(); 25 | 26 | let input = vec![ 27 | point2(70.0, 240.0), 28 | point2(145.0, 60.0), 29 | point2(275.0, 90.0), 30 | point2(300.0, 230.0), 31 | ]; 32 | let result_015 = points_on_bezier_curves(&input, 0.2, Some(0.15)); 33 | 34 | rc.fill( 35 | Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64), 36 | &background_color, 37 | ); 38 | 39 | let original_curve = CubicBez::new( 40 | Point::from(input[0].to_tuple()), 41 | Point::from(input[1].to_tuple()), 42 | Point::from(input[2].to_tuple()), 43 | Point::from(input[3].to_tuple()), 44 | ); 45 | 46 | let dpi_multiplier = 0.05; 47 | 48 | rc.stroke(original_curve, &stroke_color, 0.01 * DPI); 49 | 50 | result_015.iter().for_each(|p| { 51 | let circle = Circle::new(Point::from(p.to_tuple()), 1.0); 52 | rc.stroke(circle, &sketch_color, dpi_multiplier * DPI); 53 | }); 54 | 55 | let translation = TranslateScale::translate(Vec2::new(370.0, 0.0)); 56 | let result_075 = points_on_bezier_curves(&input, 0.2, Some(0.75)); 57 | draw_point_on_curve(&original_curve, result_075, &translation, &mut rc); 58 | 59 | let translation = TranslateScale::translate(Vec2::new(0.0, 250.0)); 60 | let result_15 = points_on_bezier_curves(&input, 0.2, Some(1.5)); 61 | draw_point_on_curve(&original_curve, result_15, &translation, &mut rc); 62 | 63 | let translation = TranslateScale::translate(Vec2::new(370.0, 250.0)); 64 | let result_30 = points_on_bezier_curves(&input, 0.2, Some(3.0)); 65 | draw_point_on_curve(&original_curve, result_30, &translation, &mut rc); 66 | 67 | rc.finish().unwrap(); 68 | std::mem::drop(rc); 69 | 70 | bitmap 71 | .save_to_file("tolerance.png") 72 | .expect("file save error"); 73 | } 74 | 75 | fn draw_point_on_curve( 76 | original_curve: &CubicBez, 77 | estimation: Vec>, 78 | translation: &TranslateScale, 79 | rc: &mut CairoRenderContext, 80 | ) { 81 | let dpi_multiplier = 0.05; 82 | 83 | let curve = *translation * *original_curve; 84 | let stroke_color = Color::from_hex_str("725752").unwrap(); 85 | rc.stroke(curve, &stroke_color, 0.01 * DPI); 86 | 87 | estimation.iter().for_each(|p| { 88 | let mut circle = Circle::new(Point::from(p.to_tuple()), 1.0); 89 | circle = *translation * circle; 90 | rc.stroke( 91 | circle, 92 | &Color::from_hex_str("FEF6C9").unwrap(), 93 | dpi_multiplier * DPI, 94 | ); 95 | }); 96 | } 97 | -------------------------------------------------------------------------------- /rough_piet/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rough_piet" 3 | version = "0.11.0" 4 | edition = "2021" 5 | authors = ["orhanbalci@gmail.com "] 6 | description = "Draw Hand Sketched 2D Drawings Using Piet" 7 | repository = "https://github.com/orhanbalci/rough-rs.git" 8 | homepage = "https://github.com/orhanbalci" 9 | keywords = ["graphics", "bezier", "sketch", "2D", "piet"] 10 | categories = ["graphics"] 11 | license = "MIT" 12 | readme = "README.md" 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | 16 | [dependencies] 17 | roughr = { path = "../roughr", version = "0.11.0" } 18 | num-traits = "0.2" 19 | euclid = "0.22" 20 | piet = "0.7" 21 | palette = "0.7" 22 | 23 | [dev-dependencies] 24 | piet-common = { version = "0.7", features = ["png"] } 25 | rand = "0.8" 26 | rand_distr = "0.4" 27 | svg_path_ops = { path = "../svg_path_ops", version = "0.11.0" } 28 | text2v = { git = "https://github.com/orhanbalci/text2v" } 29 | -------------------------------------------------------------------------------- /rough_piet/README.md: -------------------------------------------------------------------------------- 1 | 2 | # rough_piet 3 | 4 | [![Crates.io](https://img.shields.io/crates/v/rough_piet.svg)](https://crates.io/crates/rough_piet) 5 | [![Documentation](https://docs.rs/rough_piet/badge.svg)](https://docs.rs/rough_piet) 6 | [![License](https://img.shields.io/github/license/orhanbalci/rough-rs.svg)](https://github.com/orhanbalci/rough-rs/LICENSE) 7 | 8 | 9 | 10 | 11 | This crate is an adapter crate between [roughr](https://github.com/orhanbalci/rough-rs/main/roughr) and 12 | [piet](https://github.com/linebender/piet) crates. Converts from roughr drawing 13 | primitives to piets path types. Also has convenience traits for drawing onto piet contexts. For more detailed 14 | information you can check roughr crate. 15 | 16 | Below examples are output of [rough_piet](https://github.com/orhanbalci/rough-rs/tree/main/rough_piet) adapter. 17 | 18 | ## 📦 Cargo.toml 19 | 20 | ```toml 21 | [dependencies] 22 | rough_piet = "0.1" 23 | ``` 24 | 25 | ## 🔧 Example 26 | 27 | ### Rectangle 28 | 29 | ```rust 30 | let options = OptionsBuilder::default() 31 | .stroke(Srgba::from_raw(&[114u8, 87u8, 82u8, 255u8]).into_format()) 32 | .fill(Srgba::from_raw(&[254u8, 246u8, 201u8, 255u8]).into_format()) 33 | .fill_style(FillStyle::Hachure) 34 | .fill_weight(DPI * 0.01) 35 | .build() 36 | .unwrap(); 37 | let generator = KurboGenerator::new(options); 38 | let rect_width = 100.0; 39 | let rect_height = 50.0; 40 | let rect = generator.rectangle::( 41 | (WIDTH as f32 - rect_width) / 2.0, 42 | (HEIGHT as f32 - rect_height) / 2.0, 43 | rect_width, 44 | rect_height, 45 | ); 46 | let background_color = Color::from_hex_str("96C0B7").unwrap(); 47 | 48 | rc.fill( 49 | Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64), 50 | &background_color, 51 | ); 52 | rect.draw(&mut rc); 53 | ``` 54 | 55 | ### 🖨️ Output Rectangle 56 | ![rectangle](https://raw.githubusercontent.com/orhanbalci/rough-rs/main/roughr/assets/rectangle.png) 57 | 58 | ### Circle 59 | 60 | ```rust 61 | let options = OptionsBuilder::default() 62 | .stroke(Srgba::from_raw(&[114u8, 87u8, 82u8, 255u8]).into_format()) 63 | .fill(Srgba::from_raw(&[254u8, 246u8, 201u8, 255u8]).into_format()) 64 | .fill_style(FillStyle::Hachure) 65 | .fill_weight(DPI * 0.01) 66 | .build() 67 | .unwrap(); 68 | let generator = KurboGenerator::new(options); 69 | let circle_paths = generator.circle::( 70 | (WIDTH as f32) / 2.0, 71 | (HEIGHT as f32) / 2.0, 72 | HEIGHT as f32 - 10.0f32, 73 | ); 74 | let background_color = Color::from_hex_str("96C0B7").unwrap(); 75 | 76 | rc.fill( 77 | Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64), 78 | &background_color, 79 | ); 80 | circle_paths.draw(&mut rc); 81 | ``` 82 | 83 | ### 🖨️ Output Circle 84 | ![circle](https://raw.githubusercontent.com/orhanbalci/rough-rs/main/roughr/assets/circle.png) 85 | 86 | 87 | ### Ellipse 88 | 89 | ```rust 90 | let options = OptionsBuilder::default() 91 | .stroke(Srgba::from_raw(&[114u8, 87u8, 82u8, 255u8]).into_format()) 92 | .fill(Srgba::from_raw(&[254u8, 246u8, 201u8, 255u8]).into_format()) 93 | .fill_style(FillStyle::Hachure) 94 | .fill_weight(DPI * 0.01) 95 | .build() 96 | .unwrap(); 97 | let generator = KurboGenerator::new(options); 98 | let ellipse_paths = generator.ellipse::( 99 | (WIDTH as f32) / 2.0, 100 | (HEIGHT as f32) / 2.0, 101 | WIDTH as f32 - 10.0, 102 | HEIGHT as f32 - 10.0, 103 | ); 104 | let background_color = Color::from_hex_str("96C0B7").unwrap(); 105 | 106 | rc.fill( 107 | Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64), 108 | &background_color, 109 | ); 110 | ellipse_paths.draw(&mut rc); 111 | ``` 112 | 113 | ### 🖨️ Output Ellipse 114 | ![ellipse](https://raw.githubusercontent.com/orhanbalci/rough-rs/main/roughr/assets/ellipse.png) 115 | 116 | 117 | ### Svg Path 118 | 119 | ```rust 120 | let options = OptionsBuilder::default() 121 | .stroke(Srgba::from_raw(&[114u8, 87u8, 82u8, 255u8]).into_format()) 122 | .fill(Srgba::from_raw(&[254u8, 246u8, 201u8, 255u8]).into_format()) 123 | .fill_style(FillStyle::Hachure) 124 | .fill_weight(DPI * 0.01) 125 | .build() 126 | .unwrap(); 127 | let generator = KurboGenerator::new(options); 128 | let heart_svg_path = "M140 20C73 20 20 74 20 140c0 135 136 170 228 303 88-132 229-173 229-303 0-66-54-120-120-120-48 0-90 28-109 69-19-41-60-69-108-69z".into(); 129 | let heart_svg_path_drawing = generator.path::(heart_svg_path); 130 | let background_color = Color::from_hex_str("96C0B7").unwrap(); 131 | 132 | rc.fill( 133 | Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64), 134 | &background_color, 135 | ); 136 | heart_svg_path_drawing.draw(&mut rc); 137 | ``` 138 | 139 | ### 🖨️ Output Svg Path 140 | ![svgheart](https://raw.githubusercontent.com/orhanbalci/rough-rs/main/roughr/assets/heart_svg_path.png) 141 | 142 | ## Filler Implementation Status 143 | - [x] Hachure 144 | - [x] Zigzag 145 | - [x] Cross-Hatch 146 | - [x] Dots 147 | - [x] Dashed 148 | - [x] Zigzag-Line 149 | 150 | ## 🔭 Examples 151 | 152 | For more examples have a look at the 153 | [examples](https://github.com/orhanbalci/rough-rs/tree/main/rough_piet/examples) folder. 154 | 155 | 156 | 157 | ## 📝 License 158 | 159 | Licensed under MIT License ([LICENSE](LICENSE)). 160 | 161 | ### 🚧 Contributions 162 | 163 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the MIT license, shall be licensed as above, without any additional terms or conditions. 164 | -------------------------------------------------------------------------------- /rough_piet/examples/arc.rs: -------------------------------------------------------------------------------- 1 | //! This example shows painting a rough ellipse using common-piet crate and 2 | //! kurbo rough shape generator 3 | 4 | use num_traits::FloatConst; 5 | use palette::Srgba; 6 | use piet::{Color, RenderContext}; 7 | use piet_common::kurbo::Rect; 8 | use piet_common::Device; 9 | use rough_piet::KurboGenerator; 10 | use roughr::core::{FillStyle, OptionsBuilder}; 11 | 12 | const WIDTH: usize = 100; 13 | const HEIGHT: usize = 50; 14 | /// For now, assume pixel density (dots per inch) 15 | const DPI: f32 = 96.; 16 | 17 | /// Feature "png" needed for save_to_file() and it's disabled by default for optional dependencies 18 | /// cargo run --example mondrian --features png 19 | fn main() { 20 | let mut device = Device::new().unwrap(); 21 | let mut bitmap = device.bitmap_target(WIDTH, HEIGHT, 1.0).unwrap(); 22 | let mut rc = bitmap.render_context(); 23 | let options = OptionsBuilder::default() 24 | .stroke(Srgba::from_components((114u8, 87u8, 82u8, 255u8)).into_format()) 25 | .fill(Srgba::from_components((254u8, 246u8, 201u8, 255u8)).into_format()) 26 | .fill_style(FillStyle::Hachure) 27 | .fill_weight(DPI * 0.01) 28 | .build() 29 | .unwrap(); 30 | let generator = KurboGenerator::new(options); 31 | let arc_paths = generator.arc::( 32 | (WIDTH as f32) / 2.0, 33 | (HEIGHT as f32) / 2.0, 34 | WIDTH as f32, 35 | HEIGHT as f32, 36 | -f32::PI() / 2.0, 37 | f32::PI() / 2.0, 38 | false, 39 | ); 40 | let background_color = Color::from_hex_str("96C0B7").unwrap(); 41 | 42 | rc.fill( 43 | Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64), 44 | &background_color, 45 | ); 46 | arc_paths.draw(&mut rc); 47 | rc.finish().unwrap(); 48 | std::mem::drop(rc); 49 | 50 | bitmap.save_to_file("arc.png").expect("file save error"); 51 | } 52 | -------------------------------------------------------------------------------- /rough_piet/examples/bezier_cubic.rs: -------------------------------------------------------------------------------- 1 | //! This example shows painting cubic bezier curve using common-piet crate and 2 | //! kurbo rough shape generator 3 | 4 | use euclid::Point2D; 5 | use palette::Srgba; 6 | use piet::{Color, RenderContext}; 7 | use piet_common::kurbo::Rect; 8 | use piet_common::Device; 9 | use rough_piet::KurboGenerator; 10 | use roughr::core::{FillStyle, OptionsBuilder}; 11 | 12 | const WIDTH: usize = 250; 13 | const HEIGHT: usize = 200; 14 | /// For now, assume pixel density (dots per inch) 15 | const DPI: f32 = 96.; 16 | 17 | fn main() { 18 | let mut device = Device::new().unwrap(); 19 | let mut bitmap = device.bitmap_target(WIDTH, HEIGHT, 1.0).unwrap(); 20 | let mut rc = bitmap.render_context(); 21 | let options = OptionsBuilder::default() 22 | .stroke(Srgba::from_components((114u8, 87u8, 82u8, 255u8)).into_format()) 23 | .fill(Srgba::from_components((254u8, 246u8, 201u8, 255u8)).into_format()) 24 | .fill_style(FillStyle::Hachure) 25 | .fill_weight(DPI * 0.01) 26 | .build() 27 | .unwrap(); 28 | let generator = KurboGenerator::new(options); 29 | let bezier_quadratic = generator.bezier_cubic::( 30 | Point2D::new(WIDTH as f32, (1.0 / 3.0) * HEIGHT as f32), 31 | Point2D::new((2.0 / 3.0) * WIDTH as f32, (5.0 / 3.0) * HEIGHT as f32), 32 | Point2D::new((1.0 / 3.0) * WIDTH as f32, -(1.0 / 3.0) * HEIGHT as f32), 33 | Point2D::new(0.0, (2.0 / 3.0) * HEIGHT as f32), 34 | ); 35 | let background_color = Color::from_hex_str("96C0B7").unwrap(); 36 | 37 | rc.fill( 38 | Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64), 39 | &background_color, 40 | ); 41 | bezier_quadratic.draw(&mut rc); 42 | rc.finish().unwrap(); 43 | std::mem::drop(rc); 44 | 45 | bitmap 46 | .save_to_file("bezier_cubic.png") 47 | .expect("file save error"); 48 | } 49 | -------------------------------------------------------------------------------- /rough_piet/examples/bezier_quadratic.rs: -------------------------------------------------------------------------------- 1 | //! This example shows painting quadratic bezier curve using common-piet crate and 2 | //! kurbo rough shape generator 3 | 4 | use euclid::Point2D; 5 | use palette::Srgba; 6 | use piet::{Color, RenderContext}; 7 | use piet_common::kurbo::Rect; 8 | use piet_common::Device; 9 | use rough_piet::KurboGenerator; 10 | use roughr::core::{FillStyle, OptionsBuilder}; 11 | 12 | const WIDTH: usize = 250; 13 | const HEIGHT: usize = 200; 14 | /// For now, assume pixel density (dots per inch) 15 | const DPI: f32 = 96.; 16 | 17 | fn main() { 18 | let mut device = Device::new().unwrap(); 19 | let mut bitmap = device.bitmap_target(WIDTH, HEIGHT, 1.0).unwrap(); 20 | let mut rc = bitmap.render_context(); 21 | let options = OptionsBuilder::default() 22 | .stroke(Srgba::from_components((114u8, 87u8, 82u8, 255u8)).into_format()) 23 | .fill(Srgba::from_components((254u8, 246u8, 201u8, 255u8)).into_format()) 24 | .fill_style(FillStyle::Hachure) 25 | .fill_weight(DPI * 0.01) 26 | .build() 27 | .unwrap(); 28 | let generator = KurboGenerator::new(options); 29 | let bezier_quadratic = generator.bezier_quadratic::( 30 | Point2D::new(WIDTH as f32, HEIGHT as f32), 31 | Point2D::new(-(1.0 / 3.0) * WIDTH as f32, HEIGHT as f32 / 2.0), 32 | Point2D::new((2.0 / 3.0) * WIDTH as f32, 0.0), 33 | ); 34 | let background_color = Color::from_hex_str("96C0B7").unwrap(); 35 | 36 | rc.fill( 37 | Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64), 38 | &background_color, 39 | ); 40 | bezier_quadratic.draw(&mut rc); 41 | rc.finish().unwrap(); 42 | std::mem::drop(rc); 43 | 44 | bitmap 45 | .save_to_file("bezier_quadratic.png") 46 | .expect("file save error"); 47 | } 48 | -------------------------------------------------------------------------------- /rough_piet/examples/circle.rs: -------------------------------------------------------------------------------- 1 | //! This example shows painting a circle using common-piet crate and 2 | //! kurbo rough shape generator 3 | 4 | use palette::Srgba; 5 | use piet::{Color, RenderContext}; 6 | use piet_common::kurbo::Rect; 7 | use piet_common::Device; 8 | use rough_piet::KurboGenerator; 9 | use roughr::core::{FillStyle, OptionsBuilder}; 10 | 11 | const WIDTH: usize = 192; 12 | const HEIGHT: usize = 192; 13 | /// For now, assume pixel density (dots per inch) 14 | const DPI: f32 = 96.; 15 | 16 | /// Feature "png" needed for save_to_file() and it's disabled by default for optional dependencies 17 | /// cargo run --example mondrian --features png 18 | fn main() { 19 | let mut device = Device::new().unwrap(); 20 | let mut bitmap = device.bitmap_target(WIDTH, HEIGHT, 1e0).unwrap(); 21 | let mut rc = bitmap.render_context(); 22 | let options = OptionsBuilder::default() 23 | .stroke(Srgba::from_components((114u8, 87u8, 82u8, 255u8)).into_format()) 24 | .fill(Srgba::from_components((254u8, 246u8, 201u8, 255u8)).into_format()) 25 | .fill_style(FillStyle::Hachure) 26 | .fill_weight(DPI * 0.01) 27 | .build() 28 | .unwrap(); 29 | let generator = KurboGenerator::new(options); 30 | let circle_paths = generator.circle::( 31 | (WIDTH as f32) / 2.0, 32 | (HEIGHT as f32) / 2.0, 33 | HEIGHT as f32 - 10.0f32, 34 | ); 35 | let background_color = Color::from_hex_str("96C0B7").unwrap(); 36 | 37 | rc.fill( 38 | Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64), 39 | &background_color, 40 | ); 41 | circle_paths.draw(&mut rc); 42 | rc.finish().unwrap(); 43 | std::mem::drop(rc); 44 | 45 | bitmap.save_to_file("circle.png").expect("file save error"); 46 | } 47 | -------------------------------------------------------------------------------- /rough_piet/examples/curve.rs: -------------------------------------------------------------------------------- 1 | //! This example shows painting a rough circle using common-piet crate and 2 | //! kurbo rough shape generator 3 | 4 | use euclid::Point2D; 5 | use palette::Srgba; 6 | use piet::{Color, RenderContext}; 7 | use piet_common::kurbo::Rect; 8 | use piet_common::Device; 9 | use rough_piet::KurboGenerator; 10 | use roughr::core::{FillStyle, OptionsBuilder}; 11 | 12 | const WIDTH: usize = 100; 13 | const HEIGHT: usize = 50; 14 | /// For now, assume pixel density (dots per inch) 15 | const DPI: f32 = 96.; 16 | 17 | /// Feature "png" needed for save_to_file() and it's disabled by default for optional dependencies 18 | /// cargo run --example mondrian --features png 19 | fn main() { 20 | let mut device = Device::new().unwrap(); 21 | let mut bitmap = device.bitmap_target(WIDTH, HEIGHT, 1.0).unwrap(); 22 | let mut rc = bitmap.render_context(); 23 | let options = OptionsBuilder::default() 24 | .stroke(Srgba::from_components((114u8, 87u8, 82u8, 255u8)).into_format()) 25 | .fill(Srgba::from_components((254u8, 246u8, 201u8, 255u8)).into_format()) 26 | .fill_style(FillStyle::Hachure) 27 | .fill_weight(DPI * 0.01) 28 | .build() 29 | .unwrap(); 30 | let generator = KurboGenerator::new(options); 31 | let points = [ 32 | Point2D::new(0.0, HEIGHT as f32 / 2.0), 33 | Point2D::new(WIDTH as f32 / 2.0, HEIGHT as f32), 34 | Point2D::new(WIDTH as f32, HEIGHT as f32 / 2.0), 35 | Point2D::new(WIDTH as f32 / 2.0, 0.0), 36 | Point2D::new(0.0, HEIGHT as f32 / 2.0), 37 | ]; 38 | let curve = generator.curve::(&points); 39 | let background_color = Color::from_hex_str("96C0B7").unwrap(); 40 | 41 | rc.fill( 42 | Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64), 43 | &background_color, 44 | ); 45 | curve.draw(&mut rc); 46 | rc.finish().unwrap(); 47 | std::mem::drop(rc); 48 | 49 | bitmap.save_to_file("curve.png").expect("file save error"); 50 | } 51 | -------------------------------------------------------------------------------- /rough_piet/examples/curve_fill.rs: -------------------------------------------------------------------------------- 1 | //! This example shows painting a curve using common-piet crate and 2 | //! kurbo rough shape generator 3 | 4 | use euclid::default::Point2D; 5 | use palette::Srgba; 6 | use piet::{Color, RenderContext}; 7 | use piet_common::kurbo::Rect; 8 | use piet_common::Device; 9 | use rough_piet::KurboGenerator; 10 | use roughr::core::{FillStyle, OptionsBuilder}; 11 | 12 | const WIDTH: usize = 500; 13 | const HEIGHT: usize = 300; 14 | /// For now, assume pixel density (dots per inch) 15 | const DPI: f32 = 96.; 16 | 17 | fn main() { 18 | let mut device = Device::new().unwrap(); 19 | let mut bitmap = device.bitmap_target(WIDTH, HEIGHT, 1.0).unwrap(); 20 | let mut rc = bitmap.render_context(); 21 | let options = OptionsBuilder::default() 22 | .stroke(Srgba::from_components((114u8, 87u8, 82u8, 255u8)).into_format()) 23 | .fill(Srgba::from_components((254u8, 246u8, 201u8, 255)).into_format()) 24 | .fill_style(FillStyle::Hachure) 25 | .fill_weight(DPI * 0.01) 26 | .build() 27 | .unwrap(); 28 | let generator = KurboGenerator::new(options); 29 | let points = [ 30 | Point2D::new(0.0, HEIGHT as f32 / 2.0), 31 | Point2D::new((2.0 / 3.0) * WIDTH as f32, HEIGHT as f32), 32 | Point2D::new(WIDTH as f32, 0.0), 33 | ]; 34 | let curve = generator.curve::(&points); 35 | let background_color = Color::from_hex_str("96C0B7").unwrap(); 36 | 37 | rc.fill( 38 | Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64), 39 | &background_color, 40 | ); 41 | curve.draw(&mut rc); 42 | rc.finish().unwrap(); 43 | std::mem::drop(rc); 44 | 45 | bitmap.save_to_file("curve.png").expect("file save error"); 46 | } 47 | -------------------------------------------------------------------------------- /rough_piet/examples/ellipse.rs: -------------------------------------------------------------------------------- 1 | //! This example shows painting a rough ellipse using common-piet crate and 2 | //! kurbo rough shape generator 3 | 4 | use palette::Srgba; 5 | use piet::{Color, RenderContext}; 6 | use piet_common::kurbo::Rect; 7 | use piet_common::Device; 8 | use rough_piet::KurboGenerator; 9 | use roughr::core::{FillStyle, OptionsBuilder}; 10 | 11 | const WIDTH: usize = 192; 12 | const HEIGHT: usize = 108; 13 | /// For now, assume pixel density (dots per inch) 14 | const DPI: f32 = 96.; 15 | 16 | /// Feature "png" needed for save_to_file() and it's disabled by default for optional dependencies 17 | /// cargo run --example mondrian --features png 18 | fn main() { 19 | let mut device = Device::new().unwrap(); 20 | let mut bitmap = device.bitmap_target(WIDTH, HEIGHT, 1.0).unwrap(); 21 | let mut rc = bitmap.render_context(); 22 | let options = OptionsBuilder::default() 23 | .stroke(Srgba::from_components((114u8, 87u8, 82u8, 255u8)).into_format()) 24 | .fill(Srgba::from_components((254u8, 246u8, 201u8, 255)).into_format()) 25 | .fill_style(FillStyle::Hachure) 26 | .fill_weight(DPI * 0.01) 27 | .build() 28 | .unwrap(); 29 | let generator = KurboGenerator::new(options); 30 | let ellipse_paths = generator.ellipse::( 31 | (WIDTH as f32) / 2.0, 32 | (HEIGHT as f32) / 2.0, 33 | WIDTH as f32 - 10.0, 34 | HEIGHT as f32 - 10.0, 35 | ); 36 | let background_color = Color::from_hex_str("96C0B7").unwrap(); 37 | 38 | rc.fill( 39 | Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64), 40 | &background_color, 41 | ); 42 | ellipse_paths.draw(&mut rc); 43 | rc.finish().unwrap(); 44 | std::mem::drop(rc); 45 | 46 | bitmap.save_to_file("ellipse.png").expect("file save error"); 47 | } 48 | -------------------------------------------------------------------------------- /rough_piet/examples/heart_svg_path.rs: -------------------------------------------------------------------------------- 1 | //! This example shows painting a rough svg heart path using common-piet crate and 2 | //! kurbo rough shape generator 3 | 4 | use palette::Srgba; 5 | use piet::{Color, RenderContext}; 6 | use piet_common::kurbo::Rect; 7 | use piet_common::Device; 8 | use rough_piet::KurboGenerator; 9 | use roughr::core::{FillStyle, OptionsBuilder}; 10 | 11 | const WIDTH: usize = 500; 12 | const HEIGHT: usize = 500; 13 | /// For now, assume pixel density (dots per inch) 14 | const DPI: f32 = 96.; 15 | 16 | /// cargo run --example heart_svg_path 17 | fn main() { 18 | let mut device = Device::new().unwrap(); 19 | let mut bitmap = device.bitmap_target(WIDTH, HEIGHT, 1.0).unwrap(); 20 | let mut rc = bitmap.render_context(); 21 | let options = OptionsBuilder::default() 22 | .stroke(Srgba::from_components((114u8, 87u8, 82u8, 255u8)).into_format()) 23 | .fill(Srgba::from_components((254u8, 246u8, 201u8, 255)).into_format()) 24 | .fill_style(FillStyle::Dots) 25 | .fill_weight(DPI * 0.01) 26 | .build() 27 | .unwrap(); 28 | let generator = KurboGenerator::new(options); 29 | let heart_svg_path = "M140 20C73 20 20 74 20 140c0 135 136 170 228 303 88-132 229-173 229-303 0-66-54-120-120-120-48 0-90 28-109 69-19-41-60-69-108-69z".into(); 30 | let heart_svg_path_drawing = generator.path::(heart_svg_path); 31 | let background_color = Color::from_hex_str("96C0B7").unwrap(); 32 | 33 | rc.fill( 34 | Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64), 35 | &background_color, 36 | ); 37 | heart_svg_path_drawing.draw(&mut rc); 38 | rc.finish().unwrap(); 39 | std::mem::drop(rc); 40 | 41 | bitmap 42 | .save_to_file("heart_svg_path.png") 43 | .expect("file save error"); 44 | } 45 | -------------------------------------------------------------------------------- /rough_piet/examples/linear_path.rs: -------------------------------------------------------------------------------- 1 | //! This example shows painting a rough circle using common-piet crate and 2 | //! kurbo rough shape generator 3 | 4 | use euclid::Point2D; 5 | use palette::Srgba; 6 | use piet::{Color, RenderContext}; 7 | use piet_common::kurbo::Rect; 8 | use piet_common::Device; 9 | use rough_piet::KurboGenerator; 10 | use roughr::core::{FillStyle, OptionsBuilder}; 11 | 12 | const WIDTH: usize = 100; 13 | const HEIGHT: usize = 50; 14 | /// For now, assume pixel density (dots per inch) 15 | const DPI: f32 = 96.; 16 | 17 | /// Feature "png" needed for save_to_file() and it's disabled by default for optional dependencies 18 | /// cargo run --example mondrian --features png 19 | fn main() { 20 | let mut device = Device::new().unwrap(); 21 | let mut bitmap = device.bitmap_target(WIDTH, HEIGHT, 1.0).unwrap(); 22 | let mut rc = bitmap.render_context(); 23 | let options = OptionsBuilder::default() 24 | .stroke(Srgba::from_components((114u8, 87u8, 82u8, 255u8)).into_format()) 25 | .fill(Srgba::from_components((254u8, 246u8, 201u8, 255u8)).into_format()) 26 | .fill_style(FillStyle::Hachure) 27 | .fill_weight(DPI * 0.01) 28 | .build() 29 | .unwrap(); 30 | let generator = KurboGenerator::new(options); 31 | let points = [ 32 | Point2D::new(0.0, HEIGHT as f32 / 2.0), 33 | Point2D::new(WIDTH as f32 / 2.0, HEIGHT as f32), 34 | Point2D::new(WIDTH as f32, HEIGHT as f32 / 2.0), 35 | Point2D::new(WIDTH as f32 / 2.0, 0.0), 36 | ]; 37 | let linear_path = generator.linear_path::(&points, true); 38 | let background_color = Color::from_hex_str("96C0B7").unwrap(); 39 | 40 | rc.fill( 41 | Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64), 42 | &background_color, 43 | ); 44 | linear_path.draw(&mut rc); 45 | rc.finish().unwrap(); 46 | std::mem::drop(rc); 47 | 48 | bitmap 49 | .save_to_file("linear_path.png") 50 | .expect("file save error"); 51 | } 52 | -------------------------------------------------------------------------------- /rough_piet/examples/polygon.rs: -------------------------------------------------------------------------------- 1 | //! This example shows painting a rough circle using common-piet crate and 2 | //! kurbo rough shape generator 3 | 4 | use euclid::Point2D; 5 | use palette::Srgba; 6 | use piet::{Color, RenderContext}; 7 | use piet_common::kurbo::Rect; 8 | use piet_common::Device; 9 | use rough_piet::KurboGenerator; 10 | use roughr::core::{FillStyle, OptionsBuilder}; 11 | 12 | const WIDTH: usize = 100; 13 | const HEIGHT: usize = 50; 14 | /// For now, assume pixel density (dots per inch) 15 | const DPI: f32 = 96.; 16 | 17 | /// Feature "png" needed for save_to_file() and it's disabled by default for optional dependencies 18 | /// cargo run --example mondrian --features png 19 | fn main() { 20 | let mut device = Device::new().unwrap(); 21 | let mut bitmap = device.bitmap_target(WIDTH, HEIGHT, 1.0).unwrap(); 22 | let mut rc = bitmap.render_context(); 23 | let options = OptionsBuilder::default() 24 | .stroke(Srgba::from_components((114u8, 87u8, 82u8, 255u8)).into_format()) 25 | .fill(Srgba::from_components((254u8, 246u8, 201u8, 255u8)).into_format()) 26 | .fill_style(FillStyle::Hachure) 27 | .fill_weight(DPI * 0.01) 28 | .build() 29 | .unwrap(); 30 | let generator = KurboGenerator::new(options); 31 | let points = [ 32 | Point2D::new(0.0, HEIGHT as f32 / 2.0), 33 | Point2D::new(WIDTH as f32 / 2.0, HEIGHT as f32), 34 | Point2D::new(WIDTH as f32, HEIGHT as f32 / 2.0), 35 | Point2D::new(WIDTH as f32 / 2.0, 0.0), 36 | ]; 37 | let polygon = generator.polygon::(&points); 38 | let background_color = Color::from_hex_str("96C0B7").unwrap(); 39 | 40 | rc.fill( 41 | Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64), 42 | &background_color, 43 | ); 44 | polygon.draw(&mut rc); 45 | rc.finish().unwrap(); 46 | std::mem::drop(rc); 47 | 48 | bitmap.save_to_file("polygon.png").expect("file save error"); 49 | } 50 | -------------------------------------------------------------------------------- /rough_piet/examples/rectangle.rs: -------------------------------------------------------------------------------- 1 | //! This example shows painting a rough rectangle using common-piet crate and 2 | //! kurbo rough shape generator 3 | 4 | use palette::Srgba; 5 | use piet::{Color, RenderContext}; 6 | use piet_common::kurbo::Rect; 7 | use piet_common::Device; 8 | use rough_piet::KurboGenerator; 9 | use roughr::core::{FillStyle, OptionsBuilder}; 10 | 11 | const WIDTH: usize = 192; 12 | const HEIGHT: usize = 108; 13 | /// For now, assume pixel density (dots per inch) 14 | const DPI: f32 = 96.; 15 | 16 | /// Feature "png" needed for save_to_file() and it's disabled by default for optional dependencies 17 | /// cargo run --example mondrian --features png 18 | fn main() { 19 | let mut device = Device::new().unwrap(); 20 | let mut bitmap = device.bitmap_target(WIDTH, HEIGHT, 1.0).unwrap(); 21 | let mut rc = bitmap.render_context(); 22 | let options = OptionsBuilder::default() 23 | .stroke(Srgba::from_components((114u8, 87u8, 82u8, 255u8)).into_format()) 24 | .fill(Srgba::from_components((254u8, 246u8, 201u8, 255)).into_format()) 25 | .fill_style(FillStyle::ZigZagLine) 26 | .fill_weight(DPI * 0.01) 27 | .build() 28 | .unwrap(); 29 | let generator = KurboGenerator::new(options); 30 | let rect_width = 100.0; 31 | let rect_height = 50.0; 32 | let rect = generator.rectangle::( 33 | (WIDTH as f32 - rect_width) / 2.0, 34 | (HEIGHT as f32 - rect_height) / 2.0, 35 | rect_width, 36 | rect_height, 37 | ); 38 | let background_color = Color::from_hex_str("96C0B7").unwrap(); 39 | 40 | rc.fill( 41 | Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64), 42 | &background_color, 43 | ); 44 | rect.draw(&mut rc); 45 | 46 | rc.finish().unwrap(); 47 | std::mem::drop(rc); 48 | 49 | bitmap 50 | .save_to_file("rectangle.png") 51 | .expect("file save error"); 52 | } 53 | -------------------------------------------------------------------------------- /rough_piet/examples/rotate.rs: -------------------------------------------------------------------------------- 1 | //! This example shows painting a rough svg rust logo using common-piet crate and 2 | //! kurbo rough shape generator 3 | 4 | use palette::Srgba; 5 | use piet::{Color, RenderContext}; 6 | use piet_common::kurbo::Rect; 7 | use piet_common::Device; 8 | use rough_piet::KurboGenerator; 9 | use roughr::core::{FillStyle, OptionsBuilder}; 10 | use svg_path_ops::pt::PathTransformer; 11 | 12 | const WIDTH: usize = 460; 13 | const HEIGHT: usize = 250; 14 | /// For now, assume pixel density (dots per inch) 15 | const DPI: f32 = 96.; 16 | 17 | /// cargo run --example rust_logo 18 | fn main() { 19 | let mut device = Device::new().unwrap(); 20 | let mut bitmap = device.bitmap_target(WIDTH, HEIGHT, 1.0).unwrap(); 21 | let mut rc = bitmap.render_context(); 22 | let options = OptionsBuilder::default() 23 | .stroke(Srgba::from_components((114u8, 87, 82, 255u8)).into_format()) 24 | .fill(Srgba::from_components((254u8, 246, 201, 255u8)).into_format()) 25 | .fill_style(FillStyle::Hachure) 26 | .fill_weight(DPI * 0.01) 27 | .build() 28 | .unwrap(); 29 | 30 | let generator = KurboGenerator::new(options); 31 | let cat_svg_path: String = "M 201.7208 78.236 c -4.2472 -2.5448 -11.884 -17.4056 -13.5836 -19.102 c -1.6996 -1.7024 -11.4648 -5.524 -11.4648 -5.524 l -5.518 -17.4056 c -10.6168 1.274 -16.9796 18.2536 -17.4052 19.5276 c 0 0 -1.0644 29.5056 -48.188 18.044 c -33.7748 -8.216 -54.088 -16.1628 -71.1488 -0.144 c -1.0672 -1.8312 -1.7964 -3.6936 -2.3288 -5.7028 c -0.9512 -3.6248 -1.1672 -7.784 -1.164 -12.5232 c 0 -2.3224 0.0436 -4.7696 0.0468 -7.336 c -0.0032 -5.3744 -0.1972 -11.2992 -1.6184 -17.606 C 27.94 24.1544 25.2544 17.4972 20.5376 10.8648 c -2.0344 -2.8672 -6.0096 -3.5432 -8.8796 -1.5056 c -2.8668 2.0376 -3.5432 6.0124 -1.5056 8.8796 c 3.7216 5.258 5.6648 10.1472 6.7728 15.0204 c 1.0956 4.8668 1.3084 9.7588 1.3056 14.8108 c 0 2.4096 -0.0472 4.8544 -0.0472 7.336 c 0.0064 5.0584 0.1752 10.3196 1.5712 15.728 c 1.0984 4.294 3.0888 8.632 6.2032 12.6824 C 12.772 104.5832 20.0148 126.6336 20.0148 126.6336 l -13.6372 21.6964 c -0.2472 0.3912 -0.4132 0.8264 -0.4976 1.2768 L 0.0584 181.6292 c -0.2564 1.4084 0.3448 2.832 1.5276 3.6336 l 16.3916 11.1052 c 0.3504 0.2408 0.7856 0.31 1.1924 0.1972 c 0.4104 -0.1096 0.7452 -0.3976 0.9232 -0.7796 l 3.3304 -7.13 c 0.2472 -0.5352 0.1472 -1.1672 -0.2536 -1.5992 l -4.2912 -4.5792 c -0.8728 -0.9296 -1.1892 -2.2536 -0.8324 -3.4804 l 7.136 -24.4132 c 0.2316 -0.7792 0.7136 -1.4584 1.374 -1.928 l 9.5932 -6.8232 c 1.302 -0.9328 3.064 -0.8888 4.3224 0.1004 c 1.2584 0.9888 1.7152 2.6884 1.1236 4.1752 L 37.364 160.7432 c -0.488 1.2204 -0.2724 2.61 0.5604 3.624 l 24.1192 29.346 h 18.0872 c 0.7984 0 1.4464 -0.648 1.4464 -1.4428 v -9.0016 c 0 -0.7984 -0.648 -1.446 -1.4464 -1.446 h -5.7212 c -1.3368 0 -2.5636 -0.7416 -3.1928 -1.9216 l -9.7936 -16.4196 c -0.6572 -1.2424 -0.532 -2.748 0.3132 -3.8652 l 8.7792 -11.5996 c 0.6824 -0.898 1.7468 -1.43 2.8768 -1.43 h 24.4788 c 1.1488 0 2.2316 0.5504 2.9108 1.4804 c 0.6792 0.9296 0.8764 2.1252 0.532 3.2236 l -7.8904 24.864 c -0.2504 0.7828 -0.2256 1.6308 0.072 2.3976 l 5.0612 13.0392 h 15.7088 v -3.396 l -2.5976 -8.166 c -0.244 -0.7636 -0.2252 -1.5868 0.0532 -2.3408 l 10.5884 -28.7388 c 0.526 -1.4212 1.8776 -2.3632 3.3896 -2.3632 h 10.8044 c 0.9796 0 1.9184 0.4008 2.6008 1.1108 l 29.9252 41.346 c 0 0 15.2832 0 20.3788 0 c 5.0952 0 3.1236 -10.1656 -1.6964 -10.6168 c -9.1304 -0.848 -20.8016 -26.3192 -25.8968 -35.236 c -5.0956 -8.914 9.8656 -36.5196 21.2268 -37.7812 c 11.4616 -1.274 17.4056 -5.9468 19.1052 -12.316 C 205.5424 89.2748 205.968 80.7836 201.7208 78.236 z".into(); 32 | let cat_svg_path_drawing = generator.path::(cat_svg_path.clone()); 33 | let background_color = Color::from_hex_str("96C0B7").unwrap(); 34 | 35 | // rotate and translate given path 36 | let mut translated_path = PathTransformer::new(cat_svg_path); 37 | translated_path 38 | .rotate(90.0, 126.0, 140.0) 39 | .translate(220.0, 0.0); 40 | let translated_path_string = translated_path.to_string(); 41 | let translated_path_bbox = translated_path.to_box(None); 42 | 43 | // change colors etc of translated path to distinguish it from original 44 | let translated_options = OptionsBuilder::default() 45 | .stroke(Srgba::from_components((114u8, 87, 82, 255u8)).into_format()) 46 | .fill(Srgba::from_components((156u8, 1, 188, 255u8)).into_format()) 47 | .fill_style(FillStyle::Hachure) 48 | .fill_weight(DPI * 0.01) 49 | .build() 50 | .unwrap(); 51 | let translated_generator = KurboGenerator::new(translated_options); 52 | let translated_halloween_path_drawing = 53 | translated_generator.path::(translated_path_string); 54 | 55 | let bbox_options = OptionsBuilder::default() 56 | .stroke(Srgba::from_components((114u8, 87, 82, 255u8)).into_format()) 57 | .fill_weight(DPI * 0.01) 58 | .build() 59 | .unwrap(); 60 | let bbox_generator = KurboGenerator::new(bbox_options); 61 | let bbox = bbox_generator.rectangle( 62 | translated_path_bbox.min_x.unwrap_or(0.0), 63 | translated_path_bbox.min_y.unwrap_or(0.0), 64 | translated_path_bbox.width(), 65 | translated_path_bbox.height(), 66 | ); 67 | 68 | rc.fill( 69 | Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64), 70 | &background_color, 71 | ); 72 | cat_svg_path_drawing.draw(&mut rc); 73 | translated_halloween_path_drawing.draw(&mut rc); 74 | bbox.draw(&mut rc); 75 | rc.finish().unwrap(); 76 | std::mem::drop(rc); 77 | 78 | bitmap 79 | .save_to_file("rotated_cat.png") 80 | .expect("file save error"); 81 | } 82 | -------------------------------------------------------------------------------- /rough_piet/examples/rust_logo.rs: -------------------------------------------------------------------------------- 1 | //! This example shows painting a rough svg rust logo using common-piet crate and 2 | //! kurbo rough shape generator 3 | 4 | use palette::Srgba; 5 | use piet::{Color, RenderContext}; 6 | use piet_common::kurbo::Rect; 7 | use piet_common::Device; 8 | use rough_piet::KurboGenerator; 9 | use roughr::core::{FillStyle, OptionsBuilder}; 10 | 11 | const WIDTH: usize = 320; 12 | const HEIGHT: usize = 320; 13 | /// For now, assume pixel density (dots per inch) 14 | const DPI: f32 = 96.; 15 | 16 | /// cargo run --example rust_logo 17 | fn main() { 18 | let mut device = Device::new().unwrap(); 19 | let mut bitmap = device.bitmap_target(WIDTH, HEIGHT, 1.0).unwrap(); 20 | let mut rc = bitmap.render_context(); 21 | let options = OptionsBuilder::default() 22 | .stroke(Srgba::from_components((114u8, 87, 82, 255u8)).into_format()) 23 | .fill(Srgba::from_components((254u8, 246, 201, 255u8)).into_format()) 24 | .fill_style(FillStyle::Hachure) 25 | .fill_weight(DPI * 0.01) 26 | .build() 27 | .unwrap(); 28 | 29 | let generator = KurboGenerator::new(options); 30 | let heart_svg_path = "M 149.98 37.69 a 9.51 9.51 90 0 1 4.755 -8.236 c 2.9425 -1.6985 6.5675 -1.6985 9.51 0 A 9.51 9.51 90 0 1 169 37.69 c 0 5.252 -4.258 9.51 -9.51 9.51 s -9.51 -4.258 -9.51 -9.51 M 36.52 123.79 c 0 -5.252 4.2575 -9.51 9.51 -9.51 s 9.51 4.258 9.51 9.51 s -4.258 9.51 -9.51 9.51 s -9.51 -4.258 -9.51 -9.51 m 226.92 0.44 c 0 -5.252 4.258 -9.51 9.51 -9.51 s 9.51 4.258 9.51 9.51 s -4.2575 9.51 -9.51 9.51 s -9.51 -4.258 -9.51 -9.51 m -199.4 13.06 c 4.375 -1.954 6.3465 -7.0775 4.41 -11.46 l -4.22 -9.54 h 16.6 v 74.8 H 47.34 a 117.11 117.11 90 0 1 -3.79 -44.7 z m 69.42 1.84 v -22.05 h 39.52 c 2.04 0 14.4 2.36 14.4 11.6 c 0 7.68 -9.5 10.44 -17.3 10.44 z M 79.5 257.84 a 9.51 9.51 90 0 1 4.755 -8.236 c 2.9425 -1.6985 6.5675 -1.6985 9.51 0 a 9.51 9.51 90 0 1 4.755 8.236 c 0 5.252 -4.258 9.51 -9.51 9.51 s -9.51 -4.258 -9.51 -9.51 m 140.93 0.44 c 0 -5.252 4.2575 -9.51 9.51 -9.51 s 9.51 4.258 9.51 9.51 s -4.258 9.51 -9.51 9.51 s -9.51 -4.2575 -9.51 -9.51 m 2.94 -21.57 c -4.7 -1 -9.3 1.98 -10.3 6.67 l -4.77 22.28 c -31.0655 14.07 -66.7215 13.8985 -97.65 -0.47 l -4.77 -22.28 c -1 -4.7 -5.6 -7.68 -10.3 -6.67 l -19.67 4.22 c -3.655 -3.7645 -7.0525 -7.77 -10.17 -11.99 h 95.7 c 1.08 0 1.8 -0.2 1.8 -1.18 v -33.85 c 0 -1 -0.72 -1.18 -1.8 -1.18 h -28 V 170.8 h 30.27 c 2.76 0 14.77 0.8 18.62 16.14 l 5.65 25 c 1.8 5.5 9.13 16.53 16.93 16.53 h 49.4 c -3.3155 4.4345 -6.941 8.6285 -10.85 12.55 z m 53.14 -89.38 c 0.6725 6.7565 0.7565 13.559 0.25 20.33 h -12 c -1.2 0 -1.7 0.8 -1.7 1.97 v 5.52 c 0 13 -7.32 15.8 -13.74 16.53 c -6.1 0.7 -12.9 -2.56 -13.72 -6.3 c -3.6 -20.28 -9.6 -24.6 -19 -32.1 c 11.77 -7.48 24.02 -18.5 24.02 -33.27 c 0 -15.94 -10.93 -25.98 -18.38 -30.9 c -10.45 -6.9 -22.02 -8.27 -25.14 -8.27 H 72.75 a 117.1 117.1 90 0 1 65.51 -36.97 l 14.65 15.37 c 3.3 3.47 8.8 3.6 12.26 0.28 l 16.4 -15.67 c 33.8115 6.331 63.129 27.2085 80.17 57.09 l -11.22 25.34 c -1.9365 4.3825 0.035 9.506 4.41 11.46 z m 27.98 0.4 l -0.38 -3.92 l 11.56 -10.78 c 2.35 -2.2 1.47 -6.6 -1.53 -7.72 l -14.77 -5.52 l -1.16 -3.8 l 9.2 -12.8 c 1.88 -2.6 0.15 -6.75 -3 -7.27 l -15.58 -2.53 l -1.87 -3.5 l 6.55 -14.37 c 1.34 -2.93 -1.15 -6.67 -4.37 -6.55 l -15.8 0.55 l -2.5 -3.03 l 3.63 -15.4 c 0.73 -3.13 -2.44 -6.3 -5.57 -5.57 l -15.4 3.63 l -3.04 -2.5 l 0.55 -15.8 c 0.12 -3.2 -3.62 -5.7 -6.54 -4.37 l -14.36 6.55 l -3.5 -1.88 l -2.54 -15.58 c -0.5 -3.16 -4.67 -4.88 -7.27 -3 l -12.8 9.2 l -3.8 -1.15 l -5.52 -14.77 c -1.12 -3 -5.53 -3.88 -7.72 -1.54 l -10.78 11.56 l -3.92 -0.38 l -8.32 -13.45 c -1.68 -2.72 -6.2 -2.72 -7.87 0 l -8.32 13.45 l -3.92 0.38 l -10.8 -11.58 c -2.2 -2.34 -6.6 -1.47 -7.72 1.54 L 119.79 20.6 l -3.8 1.15 l -12.8 -9.2 c -2.6 -1.88 -6.76 -0.15 -7.27 3 l -2.54 15.58 l -3.5 1.88 l -14.36 -6.55 c -2.92 -1.33 -6.67 1.17 -6.54 4.37 l 0.55 15.8 l -3.04 2.5 l -15.4 -3.63 c -3.13 -0.73 -6.3 2.44 -5.57 5.57 l 3.63 15.4 l -2.5 3.03 l -15.8 -0.55 c -3.2 -0.1 -5.7 3.62 -4.37 6.55 l 6.55 14.37 l -1.88 3.5 l -15.58 2.53 c -3.16 0.5 -4.88 4.67 -3 7.27 l 9.2 12.8 l -1.16 3.8 l -14.77 5.52 c -3 1.12 -3.88 5.53 -1.53 7.72 l 11.56 10.78 l -0.38 3.92 l -13.45 8.32 c -2.72 1.68 -2.72 6.2 0 7.87 l 13.45 8.32 l 0.38 3.92 l -11.59 10.82 c -2.34 2.2 -1.47 6.6 1.53 7.72 l 14.77 5.52 l 1.16 3.8 l -9.2 12.8 c -1.87 2.6 -0.15 6.76 3 7.27 l 15.57 2.53 l 1.88 3.5 l -6.55 14.36 c -1.33 2.92 1.18 6.67 4.37 6.55 l 15.8 -0.55 l 2.5 3.04 l -3.63 15.4 c -0.73 3.12 2.44 6.3 5.57 5.56 l 15.4 -3.63 l 3.04 2.5 l -0.55 15.8 c -0.12 3.2 3.62 5.7 6.54 4.37 l 14.36 -6.55 l 3.5 1.88 l 2.54 15.57 c 0.5 3.17 4.67 4.88 7.27 3.02 l 12.8 -9.22 l 3.8 1.16 l 5.52 14.77 c 1.12 3 5.53 3.88 7.72 1.53 l 10.78 -11.56 l 3.92 0.4 l 8.32 13.45 c 1.68 2.7 6.18 2.72 7.87 0 l 8.32 -13.45 l 3.92 -0.4 l 10.78 11.56 c 2.2 2.35 6.6 1.47 7.72 -1.53 l 5.52 -14.77 l 3.8 -1.16 l 12.8 9.22 c 2.6 1.87 6.76 0.15 7.27 -3.02 l 2.54 -15.57 l 3.5 -1.88 l 14.36 6.55 c 2.92 1.33 6.66 -1.16 6.54 -4.37 l -0.55 -15.8 l 3.03 -2.5 l 15.4 3.63 c 3.13 0.73 6.3 -2.44 5.57 -5.56 l -3.63 -15.4 l 2.5 -3.04 l 15.8 0.55 c 3.2 0.13 5.7 -3.63 4.37 -6.55 l -6.55 -14.36 l 1.87 -3.5 l 15.58 -2.53 c 3.17 -0.5 4.9 -4.66 3 -7.27 l -9.2 -12.8 l 1.16 -3.8 l 14.77 -5.52 c 3 -1.13 3.88 -5.53 1.53 -7.72 l -11.56 -10.78 l 0.38 -3.92 l 13.45 -8.32 c 2.72 -1.68 2.73 -6.18 0 -7.87 z".into(); 31 | let heart_svg_path_drawing = generator.path::(heart_svg_path); 32 | let background_color = Color::from_hex_str("96C0B7").unwrap(); 33 | 34 | rc.fill( 35 | Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64), 36 | &background_color, 37 | ); 38 | heart_svg_path_drawing.draw(&mut rc); 39 | rc.finish().unwrap(); 40 | std::mem::drop(rc); 41 | 42 | bitmap.save_to_file("rust.png").expect("file save error"); 43 | } 44 | -------------------------------------------------------------------------------- /rough_piet/examples/rust_logo_alpha.rs: -------------------------------------------------------------------------------- 1 | //! This example shows painting a rough svg rust logo using common-piet crate and 2 | //! kurbo rough shape generator 3 | 4 | use palette::Srgba; 5 | use piet::{Color, RenderContext}; 6 | use piet_common::kurbo::Rect; 7 | use piet_common::Device; 8 | use rough_piet::KurboGenerator; 9 | use roughr::core::{FillStyle, OptionsBuilder}; 10 | 11 | const WIDTH: usize = 320; 12 | const HEIGHT: usize = 320; 13 | /// For now, assume pixel density (dots per inch) 14 | const DPI: f32 = 96.; 15 | 16 | /// cargo run --example rust_logo 17 | fn main() { 18 | let mut device = Device::new().unwrap(); 19 | let mut bitmap = device.bitmap_target(WIDTH, HEIGHT, 1.0).unwrap(); 20 | let mut rc = bitmap.render_context(); 21 | let options = OptionsBuilder::default() 22 | .stroke(Srgba::from_components((114u8, 87, 82, 100u8)).into_format()) 23 | .fill(Srgba::from_components((254u8, 246, 201, 100u8)).into_format()) 24 | .fill_style(FillStyle::Hachure) 25 | .fill_weight(DPI * 0.01) 26 | .build() 27 | .unwrap(); 28 | 29 | let generator = KurboGenerator::new(options); 30 | let heart_svg_path = "M 149.98 37.69 a 9.51 9.51 90 0 1 4.755 -8.236 c 2.9425 -1.6985 6.5675 -1.6985 9.51 0 A 9.51 9.51 90 0 1 169 37.69 c 0 5.252 -4.258 9.51 -9.51 9.51 s -9.51 -4.258 -9.51 -9.51 M 36.52 123.79 c 0 -5.252 4.2575 -9.51 9.51 -9.51 s 9.51 4.258 9.51 9.51 s -4.258 9.51 -9.51 9.51 s -9.51 -4.258 -9.51 -9.51 m 226.92 0.44 c 0 -5.252 4.258 -9.51 9.51 -9.51 s 9.51 4.258 9.51 9.51 s -4.2575 9.51 -9.51 9.51 s -9.51 -4.258 -9.51 -9.51 m -199.4 13.06 c 4.375 -1.954 6.3465 -7.0775 4.41 -11.46 l -4.22 -9.54 h 16.6 v 74.8 H 47.34 a 117.11 117.11 90 0 1 -3.79 -44.7 z m 69.42 1.84 v -22.05 h 39.52 c 2.04 0 14.4 2.36 14.4 11.6 c 0 7.68 -9.5 10.44 -17.3 10.44 z M 79.5 257.84 a 9.51 9.51 90 0 1 4.755 -8.236 c 2.9425 -1.6985 6.5675 -1.6985 9.51 0 a 9.51 9.51 90 0 1 4.755 8.236 c 0 5.252 -4.258 9.51 -9.51 9.51 s -9.51 -4.258 -9.51 -9.51 m 140.93 0.44 c 0 -5.252 4.2575 -9.51 9.51 -9.51 s 9.51 4.258 9.51 9.51 s -4.258 9.51 -9.51 9.51 s -9.51 -4.2575 -9.51 -9.51 m 2.94 -21.57 c -4.7 -1 -9.3 1.98 -10.3 6.67 l -4.77 22.28 c -31.0655 14.07 -66.7215 13.8985 -97.65 -0.47 l -4.77 -22.28 c -1 -4.7 -5.6 -7.68 -10.3 -6.67 l -19.67 4.22 c -3.655 -3.7645 -7.0525 -7.77 -10.17 -11.99 h 95.7 c 1.08 0 1.8 -0.2 1.8 -1.18 v -33.85 c 0 -1 -0.72 -1.18 -1.8 -1.18 h -28 V 170.8 h 30.27 c 2.76 0 14.77 0.8 18.62 16.14 l 5.65 25 c 1.8 5.5 9.13 16.53 16.93 16.53 h 49.4 c -3.3155 4.4345 -6.941 8.6285 -10.85 12.55 z m 53.14 -89.38 c 0.6725 6.7565 0.7565 13.559 0.25 20.33 h -12 c -1.2 0 -1.7 0.8 -1.7 1.97 v 5.52 c 0 13 -7.32 15.8 -13.74 16.53 c -6.1 0.7 -12.9 -2.56 -13.72 -6.3 c -3.6 -20.28 -9.6 -24.6 -19 -32.1 c 11.77 -7.48 24.02 -18.5 24.02 -33.27 c 0 -15.94 -10.93 -25.98 -18.38 -30.9 c -10.45 -6.9 -22.02 -8.27 -25.14 -8.27 H 72.75 a 117.1 117.1 90 0 1 65.51 -36.97 l 14.65 15.37 c 3.3 3.47 8.8 3.6 12.26 0.28 l 16.4 -15.67 c 33.8115 6.331 63.129 27.2085 80.17 57.09 l -11.22 25.34 c -1.9365 4.3825 0.035 9.506 4.41 11.46 z m 27.98 0.4 l -0.38 -3.92 l 11.56 -10.78 c 2.35 -2.2 1.47 -6.6 -1.53 -7.72 l -14.77 -5.52 l -1.16 -3.8 l 9.2 -12.8 c 1.88 -2.6 0.15 -6.75 -3 -7.27 l -15.58 -2.53 l -1.87 -3.5 l 6.55 -14.37 c 1.34 -2.93 -1.15 -6.67 -4.37 -6.55 l -15.8 0.55 l -2.5 -3.03 l 3.63 -15.4 c 0.73 -3.13 -2.44 -6.3 -5.57 -5.57 l -15.4 3.63 l -3.04 -2.5 l 0.55 -15.8 c 0.12 -3.2 -3.62 -5.7 -6.54 -4.37 l -14.36 6.55 l -3.5 -1.88 l -2.54 -15.58 c -0.5 -3.16 -4.67 -4.88 -7.27 -3 l -12.8 9.2 l -3.8 -1.15 l -5.52 -14.77 c -1.12 -3 -5.53 -3.88 -7.72 -1.54 l -10.78 11.56 l -3.92 -0.38 l -8.32 -13.45 c -1.68 -2.72 -6.2 -2.72 -7.87 0 l -8.32 13.45 l -3.92 0.38 l -10.8 -11.58 c -2.2 -2.34 -6.6 -1.47 -7.72 1.54 L 119.79 20.6 l -3.8 1.15 l -12.8 -9.2 c -2.6 -1.88 -6.76 -0.15 -7.27 3 l -2.54 15.58 l -3.5 1.88 l -14.36 -6.55 c -2.92 -1.33 -6.67 1.17 -6.54 4.37 l 0.55 15.8 l -3.04 2.5 l -15.4 -3.63 c -3.13 -0.73 -6.3 2.44 -5.57 5.57 l 3.63 15.4 l -2.5 3.03 l -15.8 -0.55 c -3.2 -0.1 -5.7 3.62 -4.37 6.55 l 6.55 14.37 l -1.88 3.5 l -15.58 2.53 c -3.16 0.5 -4.88 4.67 -3 7.27 l 9.2 12.8 l -1.16 3.8 l -14.77 5.52 c -3 1.12 -3.88 5.53 -1.53 7.72 l 11.56 10.78 l -0.38 3.92 l -13.45 8.32 c -2.72 1.68 -2.72 6.2 0 7.87 l 13.45 8.32 l 0.38 3.92 l -11.59 10.82 c -2.34 2.2 -1.47 6.6 1.53 7.72 l 14.77 5.52 l 1.16 3.8 l -9.2 12.8 c -1.87 2.6 -0.15 6.76 3 7.27 l 15.57 2.53 l 1.88 3.5 l -6.55 14.36 c -1.33 2.92 1.18 6.67 4.37 6.55 l 15.8 -0.55 l 2.5 3.04 l -3.63 15.4 c -0.73 3.12 2.44 6.3 5.57 5.56 l 15.4 -3.63 l 3.04 2.5 l -0.55 15.8 c -0.12 3.2 3.62 5.7 6.54 4.37 l 14.36 -6.55 l 3.5 1.88 l 2.54 15.57 c 0.5 3.17 4.67 4.88 7.27 3.02 l 12.8 -9.22 l 3.8 1.16 l 5.52 14.77 c 1.12 3 5.53 3.88 7.72 1.53 l 10.78 -11.56 l 3.92 0.4 l 8.32 13.45 c 1.68 2.7 6.18 2.72 7.87 0 l 8.32 -13.45 l 3.92 -0.4 l 10.78 11.56 c 2.2 2.35 6.6 1.47 7.72 -1.53 l 5.52 -14.77 l 3.8 -1.16 l 12.8 9.22 c 2.6 1.87 6.76 0.15 7.27 -3.02 l 2.54 -15.57 l 3.5 -1.88 l 14.36 6.55 c 2.92 1.33 6.66 -1.16 6.54 -4.37 l -0.55 -15.8 l 3.03 -2.5 l 15.4 3.63 c 3.13 0.73 6.3 -2.44 5.57 -5.56 l -3.63 -15.4 l 2.5 -3.04 l 15.8 0.55 c 3.2 0.13 5.7 -3.63 4.37 -6.55 l -6.55 -14.36 l 1.87 -3.5 l 15.58 -2.53 c 3.17 -0.5 4.9 -4.66 3 -7.27 l -9.2 -12.8 l 1.16 -3.8 l 14.77 -5.52 c 3 -1.13 3.88 -5.53 1.53 -7.72 l -11.56 -10.78 l 0.38 -3.92 l 13.45 -8.32 c 2.72 -1.68 2.73 -6.18 0 -7.87 z".into(); 31 | let heart_svg_path_drawing = generator.path::(heart_svg_path); 32 | let background_color = Color::from_hex_str("96C0B7").unwrap(); 33 | 34 | rc.fill( 35 | Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64), 36 | &background_color, 37 | ); 38 | heart_svg_path_drawing.draw(&mut rc); 39 | rc.finish().unwrap(); 40 | std::mem::drop(rc); 41 | 42 | bitmap 43 | .save_to_file("rust_alpha.png") 44 | .expect("file save error"); 45 | } 46 | -------------------------------------------------------------------------------- /rough_piet/examples/scale.rs: -------------------------------------------------------------------------------- 1 | //! This example shows painting a rough svg rust logo using common-piet crate and 2 | //! kurbo rough shape generator 3 | 4 | use palette::Srgba; 5 | use piet::{Color, RenderContext}; 6 | use piet_common::kurbo::Rect; 7 | use piet_common::Device; 8 | use rough_piet::KurboGenerator; 9 | use roughr::core::{FillStyle, OptionsBuilder}; 10 | use svg_path_ops::pt::PathTransformer; 11 | 12 | const WIDTH: usize = 460; 13 | const HEIGHT: usize = 250; 14 | /// For now, assume pixel density (dots per inch) 15 | const DPI: f32 = 96.; 16 | 17 | /// cargo run --example rust_logo 18 | fn main() { 19 | let mut device = Device::new().unwrap(); 20 | let mut bitmap = device.bitmap_target(WIDTH, HEIGHT, 1.0).unwrap(); 21 | let mut rc = bitmap.render_context(); 22 | let options = OptionsBuilder::default() 23 | .stroke(Srgba::from_components((114u8, 87, 82, 255u8)).into_format()) 24 | .fill(Srgba::from_components((254u8, 246, 201, 255u8)).into_format()) 25 | .fill_style(FillStyle::Hachure) 26 | .fill_weight(DPI * 0.01) 27 | .build() 28 | .unwrap(); 29 | 30 | let generator = KurboGenerator::new(options); 31 | let cat_svg_path: String = "M 201.7208 78.236 c -4.2472 -2.5448 -11.884 -17.4056 -13.5836 -19.102 c -1.6996 -1.7024 -11.4648 -5.524 -11.4648 -5.524 l -5.518 -17.4056 c -10.6168 1.274 -16.9796 18.2536 -17.4052 19.5276 c 0 0 -1.0644 29.5056 -48.188 18.044 c -33.7748 -8.216 -54.088 -16.1628 -71.1488 -0.144 c -1.0672 -1.8312 -1.7964 -3.6936 -2.3288 -5.7028 c -0.9512 -3.6248 -1.1672 -7.784 -1.164 -12.5232 c 0 -2.3224 0.0436 -4.7696 0.0468 -7.336 c -0.0032 -5.3744 -0.1972 -11.2992 -1.6184 -17.606 C 27.94 24.1544 25.2544 17.4972 20.5376 10.8648 c -2.0344 -2.8672 -6.0096 -3.5432 -8.8796 -1.5056 c -2.8668 2.0376 -3.5432 6.0124 -1.5056 8.8796 c 3.7216 5.258 5.6648 10.1472 6.7728 15.0204 c 1.0956 4.8668 1.3084 9.7588 1.3056 14.8108 c 0 2.4096 -0.0472 4.8544 -0.0472 7.336 c 0.0064 5.0584 0.1752 10.3196 1.5712 15.728 c 1.0984 4.294 3.0888 8.632 6.2032 12.6824 C 12.772 104.5832 20.0148 126.6336 20.0148 126.6336 l -13.6372 21.6964 c -0.2472 0.3912 -0.4132 0.8264 -0.4976 1.2768 L 0.0584 181.6292 c -0.2564 1.4084 0.3448 2.832 1.5276 3.6336 l 16.3916 11.1052 c 0.3504 0.2408 0.7856 0.31 1.1924 0.1972 c 0.4104 -0.1096 0.7452 -0.3976 0.9232 -0.7796 l 3.3304 -7.13 c 0.2472 -0.5352 0.1472 -1.1672 -0.2536 -1.5992 l -4.2912 -4.5792 c -0.8728 -0.9296 -1.1892 -2.2536 -0.8324 -3.4804 l 7.136 -24.4132 c 0.2316 -0.7792 0.7136 -1.4584 1.374 -1.928 l 9.5932 -6.8232 c 1.302 -0.9328 3.064 -0.8888 4.3224 0.1004 c 1.2584 0.9888 1.7152 2.6884 1.1236 4.1752 L 37.364 160.7432 c -0.488 1.2204 -0.2724 2.61 0.5604 3.624 l 24.1192 29.346 h 18.0872 c 0.7984 0 1.4464 -0.648 1.4464 -1.4428 v -9.0016 c 0 -0.7984 -0.648 -1.446 -1.4464 -1.446 h -5.7212 c -1.3368 0 -2.5636 -0.7416 -3.1928 -1.9216 l -9.7936 -16.4196 c -0.6572 -1.2424 -0.532 -2.748 0.3132 -3.8652 l 8.7792 -11.5996 c 0.6824 -0.898 1.7468 -1.43 2.8768 -1.43 h 24.4788 c 1.1488 0 2.2316 0.5504 2.9108 1.4804 c 0.6792 0.9296 0.8764 2.1252 0.532 3.2236 l -7.8904 24.864 c -0.2504 0.7828 -0.2256 1.6308 0.072 2.3976 l 5.0612 13.0392 h 15.7088 v -3.396 l -2.5976 -8.166 c -0.244 -0.7636 -0.2252 -1.5868 0.0532 -2.3408 l 10.5884 -28.7388 c 0.526 -1.4212 1.8776 -2.3632 3.3896 -2.3632 h 10.8044 c 0.9796 0 1.9184 0.4008 2.6008 1.1108 l 29.9252 41.346 c 0 0 15.2832 0 20.3788 0 c 5.0952 0 3.1236 -10.1656 -1.6964 -10.6168 c -9.1304 -0.848 -20.8016 -26.3192 -25.8968 -35.236 c -5.0956 -8.914 9.8656 -36.5196 21.2268 -37.7812 c 11.4616 -1.274 17.4056 -5.9468 19.1052 -12.316 C 205.5424 89.2748 205.968 80.7836 201.7208 78.236 z".into(); 32 | let cat_svg_path_drawing = generator.path::(cat_svg_path.clone()); 33 | let background_color = Color::from_hex_str("96C0B7").unwrap(); 34 | 35 | // rotate and translate given path 36 | let mut translated_path = PathTransformer::new(cat_svg_path); 37 | translated_path.scale(0.5, 0.5).translate(220.0, 60.0); 38 | let translated_path_string = translated_path.to_string(); 39 | let translated_path_bbox = translated_path.to_box(None); 40 | 41 | // change colors etc of translated path to distinguish it from original 42 | let translated_options = OptionsBuilder::default() 43 | .stroke(Srgba::from_components((114u8, 87, 82, 255u8)).into_format()) 44 | .fill(Srgba::from_components((156u8, 1, 188, 255u8)).into_format()) 45 | .fill_style(FillStyle::Hachure) 46 | .fill_weight(DPI * 0.01) 47 | .build() 48 | .unwrap(); 49 | let translated_generator = KurboGenerator::new(translated_options); 50 | let translated_cat_path_drawing = translated_generator.path::(translated_path_string); 51 | 52 | let bbox_options = OptionsBuilder::default() 53 | .stroke(Srgba::from_components((114u8, 87, 82, 255u8)).into_format()) 54 | .fill_weight(DPI * 0.01) 55 | .build() 56 | .unwrap(); 57 | let bbox_generator = KurboGenerator::new(bbox_options); 58 | let bbox = bbox_generator.rectangle( 59 | translated_path_bbox.min_x.unwrap_or(0.0), 60 | translated_path_bbox.min_y.unwrap_or(0.0), 61 | translated_path_bbox.width(), 62 | translated_path_bbox.height(), 63 | ); 64 | 65 | rc.fill( 66 | Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64), 67 | &background_color, 68 | ); 69 | cat_svg_path_drawing.draw(&mut rc); 70 | translated_cat_path_drawing.draw(&mut rc); 71 | bbox.draw(&mut rc); 72 | rc.finish().unwrap(); 73 | std::mem::drop(rc); 74 | 75 | bitmap 76 | .save_to_file("scaled_cat.png") 77 | .expect("file save error"); 78 | } 79 | -------------------------------------------------------------------------------- /rough_piet/examples/skew.rs: -------------------------------------------------------------------------------- 1 | //! This example shows painting a rough svg rust logo using common-piet crate and 2 | //! kurbo rough shape generator 3 | 4 | use palette::Srgba; 5 | use piet::{Color, RenderContext}; 6 | use piet_common::kurbo::Rect; 7 | use piet_common::Device; 8 | use rough_piet::KurboGenerator; 9 | use roughr::core::{FillStyle, OptionsBuilder}; 10 | use svg_path_ops::pt::PathTransformer; 11 | 12 | const WIDTH: usize = 460; 13 | const HEIGHT: usize = 250; 14 | /// For now, assume pixel density (dots per inch) 15 | const DPI: f32 = 96.; 16 | 17 | /// cargo run --example rust_logo 18 | fn main() { 19 | let mut device = Device::new().unwrap(); 20 | let mut bitmap = device.bitmap_target(WIDTH, HEIGHT, 1.0).unwrap(); 21 | let mut rc = bitmap.render_context(); 22 | let options = OptionsBuilder::default() 23 | .stroke(Srgba::from_components((114u8, 87, 82, 255u8)).into_format()) 24 | .fill(Srgba::from_components((254u8, 246, 201, 255u8)).into_format()) 25 | .fill_style(FillStyle::Hachure) 26 | .fill_weight(DPI * 0.01) 27 | .build() 28 | .unwrap(); 29 | 30 | let generator = KurboGenerator::new(options); 31 | let cat_svg_path: String = "M 201.7208 78.236 c -4.2472 -2.5448 -11.884 -17.4056 -13.5836 -19.102 c -1.6996 -1.7024 -11.4648 -5.524 -11.4648 -5.524 l -5.518 -17.4056 c -10.6168 1.274 -16.9796 18.2536 -17.4052 19.5276 c 0 0 -1.0644 29.5056 -48.188 18.044 c -33.7748 -8.216 -54.088 -16.1628 -71.1488 -0.144 c -1.0672 -1.8312 -1.7964 -3.6936 -2.3288 -5.7028 c -0.9512 -3.6248 -1.1672 -7.784 -1.164 -12.5232 c 0 -2.3224 0.0436 -4.7696 0.0468 -7.336 c -0.0032 -5.3744 -0.1972 -11.2992 -1.6184 -17.606 C 27.94 24.1544 25.2544 17.4972 20.5376 10.8648 c -2.0344 -2.8672 -6.0096 -3.5432 -8.8796 -1.5056 c -2.8668 2.0376 -3.5432 6.0124 -1.5056 8.8796 c 3.7216 5.258 5.6648 10.1472 6.7728 15.0204 c 1.0956 4.8668 1.3084 9.7588 1.3056 14.8108 c 0 2.4096 -0.0472 4.8544 -0.0472 7.336 c 0.0064 5.0584 0.1752 10.3196 1.5712 15.728 c 1.0984 4.294 3.0888 8.632 6.2032 12.6824 C 12.772 104.5832 20.0148 126.6336 20.0148 126.6336 l -13.6372 21.6964 c -0.2472 0.3912 -0.4132 0.8264 -0.4976 1.2768 L 0.0584 181.6292 c -0.2564 1.4084 0.3448 2.832 1.5276 3.6336 l 16.3916 11.1052 c 0.3504 0.2408 0.7856 0.31 1.1924 0.1972 c 0.4104 -0.1096 0.7452 -0.3976 0.9232 -0.7796 l 3.3304 -7.13 c 0.2472 -0.5352 0.1472 -1.1672 -0.2536 -1.5992 l -4.2912 -4.5792 c -0.8728 -0.9296 -1.1892 -2.2536 -0.8324 -3.4804 l 7.136 -24.4132 c 0.2316 -0.7792 0.7136 -1.4584 1.374 -1.928 l 9.5932 -6.8232 c 1.302 -0.9328 3.064 -0.8888 4.3224 0.1004 c 1.2584 0.9888 1.7152 2.6884 1.1236 4.1752 L 37.364 160.7432 c -0.488 1.2204 -0.2724 2.61 0.5604 3.624 l 24.1192 29.346 h 18.0872 c 0.7984 0 1.4464 -0.648 1.4464 -1.4428 v -9.0016 c 0 -0.7984 -0.648 -1.446 -1.4464 -1.446 h -5.7212 c -1.3368 0 -2.5636 -0.7416 -3.1928 -1.9216 l -9.7936 -16.4196 c -0.6572 -1.2424 -0.532 -2.748 0.3132 -3.8652 l 8.7792 -11.5996 c 0.6824 -0.898 1.7468 -1.43 2.8768 -1.43 h 24.4788 c 1.1488 0 2.2316 0.5504 2.9108 1.4804 c 0.6792 0.9296 0.8764 2.1252 0.532 3.2236 l -7.8904 24.864 c -0.2504 0.7828 -0.2256 1.6308 0.072 2.3976 l 5.0612 13.0392 h 15.7088 v -3.396 l -2.5976 -8.166 c -0.244 -0.7636 -0.2252 -1.5868 0.0532 -2.3408 l 10.5884 -28.7388 c 0.526 -1.4212 1.8776 -2.3632 3.3896 -2.3632 h 10.8044 c 0.9796 0 1.9184 0.4008 2.6008 1.1108 l 29.9252 41.346 c 0 0 15.2832 0 20.3788 0 c 5.0952 0 3.1236 -10.1656 -1.6964 -10.6168 c -9.1304 -0.848 -20.8016 -26.3192 -25.8968 -35.236 c -5.0956 -8.914 9.8656 -36.5196 21.2268 -37.7812 c 11.4616 -1.274 17.4056 -5.9468 19.1052 -12.316 C 205.5424 89.2748 205.968 80.7836 201.7208 78.236 z".into(); 32 | let cat_svg_path_drawing = generator.path::(cat_svg_path.clone()); 33 | let background_color = Color::from_hex_str("96C0B7").unwrap(); 34 | 35 | // rotate and translate given path 36 | let mut translated_path = PathTransformer::new(cat_svg_path); 37 | translated_path.skew_x(20.0).translate(180.0, 0.0); 38 | let translated_path_string = translated_path.to_string(); 39 | let translated_path_bbox = translated_path.to_box(None); 40 | 41 | // change colors etc of translated path to distinguish it from original 42 | let translated_options = OptionsBuilder::default() 43 | .stroke(Srgba::from_components((114u8, 87, 82, 255u8)).into_format()) 44 | .fill(Srgba::from_components((156u8, 1, 188, 255u8)).into_format()) 45 | .fill_style(FillStyle::Hachure) 46 | .fill_weight(DPI * 0.01) 47 | .build() 48 | .unwrap(); 49 | let translated_generator = KurboGenerator::new(translated_options); 50 | let translated_cat_path_drawing = translated_generator.path::(translated_path_string); 51 | 52 | let bbox_options = OptionsBuilder::default() 53 | .stroke(Srgba::from_components((114u8, 87, 82, 255u8)).into_format()) 54 | .fill_weight(DPI * 0.01) 55 | .build() 56 | .unwrap(); 57 | let bbox_generator = KurboGenerator::new(bbox_options); 58 | let bbox = bbox_generator.rectangle( 59 | translated_path_bbox.min_x.unwrap_or(0.0), 60 | translated_path_bbox.min_y.unwrap_or(0.0), 61 | translated_path_bbox.width(), 62 | translated_path_bbox.height(), 63 | ); 64 | 65 | rc.fill( 66 | Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64), 67 | &background_color, 68 | ); 69 | cat_svg_path_drawing.draw(&mut rc); 70 | translated_cat_path_drawing.draw(&mut rc); 71 | bbox.draw(&mut rc); 72 | rc.finish().unwrap(); 73 | std::mem::drop(rc); 74 | 75 | bitmap 76 | .save_to_file("skewed_cat.png") 77 | .expect("file save error"); 78 | } 79 | -------------------------------------------------------------------------------- /rough_piet/examples/skewy.rs: -------------------------------------------------------------------------------- 1 | //! This example shows painting a rough svg rust logo using common-piet crate and 2 | //! kurbo rough shape generator 3 | 4 | use palette::Srgba; 5 | use piet::{Color, RenderContext}; 6 | use piet_common::kurbo::Rect; 7 | use piet_common::Device; 8 | use rough_piet::KurboGenerator; 9 | use roughr::core::{FillStyle, OptionsBuilder}; 10 | use svg_path_ops::pt::PathTransformer; 11 | 12 | const WIDTH: usize = 460; 13 | const HEIGHT: usize = 250; 14 | /// For now, assume pixel density (dots per inch) 15 | const DPI: f32 = 96.; 16 | 17 | /// cargo run --example rust_logo 18 | fn main() { 19 | let mut device = Device::new().unwrap(); 20 | let mut bitmap = device.bitmap_target(WIDTH, HEIGHT, 1.0).unwrap(); 21 | let mut rc = bitmap.render_context(); 22 | let options = OptionsBuilder::default() 23 | .stroke(Srgba::from_components((114u8, 87, 82, 255u8)).into_format()) 24 | .fill(Srgba::from_components((254u8, 246, 201, 255u8)).into_format()) 25 | .fill_style(FillStyle::Hachure) 26 | .fill_weight(DPI * 0.01) 27 | .build() 28 | .unwrap(); 29 | 30 | let generator = KurboGenerator::new(options); 31 | let cat_svg_path: String = "M 201.7208 78.236 c -4.2472 -2.5448 -11.884 -17.4056 -13.5836 -19.102 c -1.6996 -1.7024 -11.4648 -5.524 -11.4648 -5.524 l -5.518 -17.4056 c -10.6168 1.274 -16.9796 18.2536 -17.4052 19.5276 c 0 0 -1.0644 29.5056 -48.188 18.044 c -33.7748 -8.216 -54.088 -16.1628 -71.1488 -0.144 c -1.0672 -1.8312 -1.7964 -3.6936 -2.3288 -5.7028 c -0.9512 -3.6248 -1.1672 -7.784 -1.164 -12.5232 c 0 -2.3224 0.0436 -4.7696 0.0468 -7.336 c -0.0032 -5.3744 -0.1972 -11.2992 -1.6184 -17.606 C 27.94 24.1544 25.2544 17.4972 20.5376 10.8648 c -2.0344 -2.8672 -6.0096 -3.5432 -8.8796 -1.5056 c -2.8668 2.0376 -3.5432 6.0124 -1.5056 8.8796 c 3.7216 5.258 5.6648 10.1472 6.7728 15.0204 c 1.0956 4.8668 1.3084 9.7588 1.3056 14.8108 c 0 2.4096 -0.0472 4.8544 -0.0472 7.336 c 0.0064 5.0584 0.1752 10.3196 1.5712 15.728 c 1.0984 4.294 3.0888 8.632 6.2032 12.6824 C 12.772 104.5832 20.0148 126.6336 20.0148 126.6336 l -13.6372 21.6964 c -0.2472 0.3912 -0.4132 0.8264 -0.4976 1.2768 L 0.0584 181.6292 c -0.2564 1.4084 0.3448 2.832 1.5276 3.6336 l 16.3916 11.1052 c 0.3504 0.2408 0.7856 0.31 1.1924 0.1972 c 0.4104 -0.1096 0.7452 -0.3976 0.9232 -0.7796 l 3.3304 -7.13 c 0.2472 -0.5352 0.1472 -1.1672 -0.2536 -1.5992 l -4.2912 -4.5792 c -0.8728 -0.9296 -1.1892 -2.2536 -0.8324 -3.4804 l 7.136 -24.4132 c 0.2316 -0.7792 0.7136 -1.4584 1.374 -1.928 l 9.5932 -6.8232 c 1.302 -0.9328 3.064 -0.8888 4.3224 0.1004 c 1.2584 0.9888 1.7152 2.6884 1.1236 4.1752 L 37.364 160.7432 c -0.488 1.2204 -0.2724 2.61 0.5604 3.624 l 24.1192 29.346 h 18.0872 c 0.7984 0 1.4464 -0.648 1.4464 -1.4428 v -9.0016 c 0 -0.7984 -0.648 -1.446 -1.4464 -1.446 h -5.7212 c -1.3368 0 -2.5636 -0.7416 -3.1928 -1.9216 l -9.7936 -16.4196 c -0.6572 -1.2424 -0.532 -2.748 0.3132 -3.8652 l 8.7792 -11.5996 c 0.6824 -0.898 1.7468 -1.43 2.8768 -1.43 h 24.4788 c 1.1488 0 2.2316 0.5504 2.9108 1.4804 c 0.6792 0.9296 0.8764 2.1252 0.532 3.2236 l -7.8904 24.864 c -0.2504 0.7828 -0.2256 1.6308 0.072 2.3976 l 5.0612 13.0392 h 15.7088 v -3.396 l -2.5976 -8.166 c -0.244 -0.7636 -0.2252 -1.5868 0.0532 -2.3408 l 10.5884 -28.7388 c 0.526 -1.4212 1.8776 -2.3632 3.3896 -2.3632 h 10.8044 c 0.9796 0 1.9184 0.4008 2.6008 1.1108 l 29.9252 41.346 c 0 0 15.2832 0 20.3788 0 c 5.0952 0 3.1236 -10.1656 -1.6964 -10.6168 c -9.1304 -0.848 -20.8016 -26.3192 -25.8968 -35.236 c -5.0956 -8.914 9.8656 -36.5196 21.2268 -37.7812 c 11.4616 -1.274 17.4056 -5.9468 19.1052 -12.316 C 205.5424 89.2748 205.968 80.7836 201.7208 78.236 z".into(); 32 | let cat_svg_path_drawing = generator.path::(cat_svg_path.clone()); 33 | let background_color = Color::from_hex_str("96C0B7").unwrap(); 34 | 35 | // rotate and translate given path 36 | let translated_path = PathTransformer::new(cat_svg_path) 37 | .skew_y(20.0) 38 | .translate(180.0, 0.0) 39 | .to_string(); 40 | 41 | // change colors etc of translated path to distinguish it from original 42 | let translated_options = OptionsBuilder::default() 43 | .stroke(Srgba::from_components((114u8, 87, 82, 255u8)).into_format()) 44 | .fill(Srgba::from_components((156u8, 1, 188, 255u8)).into_format()) 45 | .fill_style(FillStyle::Hachure) 46 | .fill_weight(DPI * 0.01) 47 | .build() 48 | .unwrap(); 49 | let translated_generator = KurboGenerator::new(translated_options); 50 | let translated_cat_path_drawing = translated_generator.path::(translated_path); 51 | 52 | rc.fill( 53 | Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64), 54 | &background_color, 55 | ); 56 | cat_svg_path_drawing.draw(&mut rc); 57 | translated_cat_path_drawing.draw(&mut rc); 58 | rc.finish().unwrap(); 59 | std::mem::drop(rc); 60 | 61 | bitmap 62 | .save_to_file("skewedy_cat.png") 63 | .expect("file save error"); 64 | } 65 | -------------------------------------------------------------------------------- /rough_piet/examples/stroke_style.rs: -------------------------------------------------------------------------------- 1 | //! This example shows painting a rough rectangle using common-piet crate and 2 | //! kurbo rough shape generator 3 | 4 | use palette::Srgba; 5 | use piet::{Color, RenderContext}; 6 | use piet_common::kurbo::Rect; 7 | use piet_common::Device; 8 | use rough_piet::KurboGenerator; 9 | use roughr::core::{LineCap, LineJoin, OptionsBuilder}; 10 | 11 | const WIDTH: usize = 192; 12 | const HEIGHT: usize = 108; 13 | /// For now, assume pixel density (dots per inch) 14 | const DPI: f32 = 96.; 15 | 16 | /// Feature "png" needed for save_to_file() and it's disabled by default for optional dependencies 17 | /// cargo run --example mondrian --features png 18 | fn main() { 19 | let mut device = Device::new().unwrap(); 20 | let mut bitmap = device.bitmap_target(WIDTH, HEIGHT, 1.0).unwrap(); 21 | let mut rc = bitmap.render_context(); 22 | let options = OptionsBuilder::default() 23 | .stroke(Srgba::from_components((114u8, 87u8, 82u8, 255u8)).into_format()) 24 | .stroke_width(5.0) 25 | .roughness(0.0) 26 | .stroke_line_dash(vec![]) 27 | .line_cap(LineCap::Round) 28 | .line_join(LineJoin::Round) 29 | .build() 30 | .unwrap(); 31 | let generator = KurboGenerator::new(options); 32 | let rect_width = 100.0; 33 | let rect_height = 50.0; 34 | let rect = generator.rectangle::( 35 | (WIDTH as f32 - rect_width) / 2.0, 36 | (HEIGHT as f32 - rect_height) / 2.0, 37 | rect_width, 38 | rect_height, 39 | ); 40 | let background_color = Color::from_hex_str("96C0B7").unwrap(); 41 | 42 | rc.fill( 43 | Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64), 44 | &background_color, 45 | ); 46 | rect.draw(&mut rc); 47 | 48 | rc.finish().unwrap(); 49 | std::mem::drop(rc); 50 | 51 | bitmap 52 | .save_to_file("stroke_style.png") 53 | .expect("file save error"); 54 | } 55 | -------------------------------------------------------------------------------- /rough_piet/examples/text.rs: -------------------------------------------------------------------------------- 1 | use piet::{Color, RenderContext}; 2 | use piet_common::kurbo::{Affine, BezPath, Rect, Shape}; 3 | use piet_common::Device; 4 | use rough_piet::KurboGenerator; 5 | use roughr::core::{FillStyle, OptionsBuilder}; 6 | use roughr::Srgba; 7 | 8 | const WIDTH: usize = 500; 9 | const HEIGHT: usize = 200; 10 | 11 | /// Feature "png" needed for save_to_file() and it's disabled by default for optional dependencies 12 | /// cargo run --example mondrian --features png 13 | fn main() { 14 | let mut device = Device::new().unwrap(); 15 | let mut bitmap = device.bitmap_target(WIDTH, HEIGHT, 1.0).unwrap(); 16 | let mut rc = bitmap.render_context(); 17 | 18 | let background_color = Color::from_hex_str("96C0B7").unwrap(); 19 | 20 | rc.fill( 21 | Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64), 22 | &background_color, 23 | ); 24 | 25 | let mut f = text2v::monospace_font(); 26 | let path_iter = f.render("rough-rs", 2000.0); 27 | let mut combined = BezPath::from_iter(path_iter.0); 28 | combined.apply_affine(Affine::scale(100.0)); 29 | let bb = combined.bounding_box(); 30 | combined.apply_affine(Affine::translate(( 31 | (WIDTH as f64 / 2.0) - (bb.width() / 2.0), 32 | (HEIGHT as f64 / 2.0) - (bb.height() / 2.0), 33 | ))); 34 | 35 | const DPI: f32 = 96.0; 36 | let text_options = OptionsBuilder::default() 37 | .stroke(Srgba::from_components((114u8, 87, 82, 255u8)).into_format()) 38 | .fill(Srgba::from_components((254u8, 246, 201, 255u8)).into_format()) 39 | .fill_style(FillStyle::CrossHatch) 40 | .fill_weight(DPI * 0.01) 41 | .build() 42 | .unwrap(); 43 | let text_generator = KurboGenerator::new(text_options); 44 | let text_rough = text_generator.bez_path::(combined); 45 | text_rough.draw(&mut rc); 46 | rc.finish().unwrap(); 47 | std::mem::drop(rc); 48 | 49 | bitmap 50 | .save_to_file("rough_text.png") 51 | .expect("file save error"); 52 | } 53 | -------------------------------------------------------------------------------- /rough_piet/examples/translate.rs: -------------------------------------------------------------------------------- 1 | //! This example shows painting a rough svg rust logo using common-piet crate and 2 | //! kurbo rough shape generator 3 | 4 | use palette::Srgba; 5 | use piet::{Color, RenderContext}; 6 | use piet_common::kurbo::Rect; 7 | use piet_common::Device; 8 | use rough_piet::KurboGenerator; 9 | use roughr::core::{FillStyle, OptionsBuilder}; 10 | use svg_path_ops::pt::PathTransformer; 11 | 12 | const WIDTH: usize = 460; 13 | const HEIGHT: usize = 230; 14 | /// For now, assume pixel density (dots per inch) 15 | const DPI: f32 = 96.; 16 | 17 | /// cargo run --example rust_logo 18 | fn main() { 19 | let mut device = Device::new().unwrap(); 20 | let mut bitmap = device.bitmap_target(WIDTH, HEIGHT, 1.0).unwrap(); 21 | let mut rc = bitmap.render_context(); 22 | let options = OptionsBuilder::default() 23 | .stroke(Srgba::from_components((114u8, 87, 82, 255u8)).into_format()) 24 | .fill(Srgba::from_components((254u8, 246, 201, 255u8)).into_format()) 25 | .fill_style(FillStyle::Hachure) 26 | .fill_weight(DPI * 0.01) 27 | .build() 28 | .unwrap(); 29 | 30 | let generator = KurboGenerator::new(options); 31 | let cat_svg_path: String = "M 201.7208 78.236 c -4.2472 -2.5448 -11.884 -17.4056 -13.5836 -19.102 c -1.6996 -1.7024 -11.4648 -5.524 -11.4648 -5.524 l -5.518 -17.4056 c -10.6168 1.274 -16.9796 18.2536 -17.4052 19.5276 c 0 0 -1.0644 29.5056 -48.188 18.044 c -33.7748 -8.216 -54.088 -16.1628 -71.1488 -0.144 c -1.0672 -1.8312 -1.7964 -3.6936 -2.3288 -5.7028 c -0.9512 -3.6248 -1.1672 -7.784 -1.164 -12.5232 c 0 -2.3224 0.0436 -4.7696 0.0468 -7.336 c -0.0032 -5.3744 -0.1972 -11.2992 -1.6184 -17.606 C 27.94 24.1544 25.2544 17.4972 20.5376 10.8648 c -2.0344 -2.8672 -6.0096 -3.5432 -8.8796 -1.5056 c -2.8668 2.0376 -3.5432 6.0124 -1.5056 8.8796 c 3.7216 5.258 5.6648 10.1472 6.7728 15.0204 c 1.0956 4.8668 1.3084 9.7588 1.3056 14.8108 c 0 2.4096 -0.0472 4.8544 -0.0472 7.336 c 0.0064 5.0584 0.1752 10.3196 1.5712 15.728 c 1.0984 4.294 3.0888 8.632 6.2032 12.6824 C 12.772 104.5832 20.0148 126.6336 20.0148 126.6336 l -13.6372 21.6964 c -0.2472 0.3912 -0.4132 0.8264 -0.4976 1.2768 L 0.0584 181.6292 c -0.2564 1.4084 0.3448 2.832 1.5276 3.6336 l 16.3916 11.1052 c 0.3504 0.2408 0.7856 0.31 1.1924 0.1972 c 0.4104 -0.1096 0.7452 -0.3976 0.9232 -0.7796 l 3.3304 -7.13 c 0.2472 -0.5352 0.1472 -1.1672 -0.2536 -1.5992 l -4.2912 -4.5792 c -0.8728 -0.9296 -1.1892 -2.2536 -0.8324 -3.4804 l 7.136 -24.4132 c 0.2316 -0.7792 0.7136 -1.4584 1.374 -1.928 l 9.5932 -6.8232 c 1.302 -0.9328 3.064 -0.8888 4.3224 0.1004 c 1.2584 0.9888 1.7152 2.6884 1.1236 4.1752 L 37.364 160.7432 c -0.488 1.2204 -0.2724 2.61 0.5604 3.624 l 24.1192 29.346 h 18.0872 c 0.7984 0 1.4464 -0.648 1.4464 -1.4428 v -9.0016 c 0 -0.7984 -0.648 -1.446 -1.4464 -1.446 h -5.7212 c -1.3368 0 -2.5636 -0.7416 -3.1928 -1.9216 l -9.7936 -16.4196 c -0.6572 -1.2424 -0.532 -2.748 0.3132 -3.8652 l 8.7792 -11.5996 c 0.6824 -0.898 1.7468 -1.43 2.8768 -1.43 h 24.4788 c 1.1488 0 2.2316 0.5504 2.9108 1.4804 c 0.6792 0.9296 0.8764 2.1252 0.532 3.2236 l -7.8904 24.864 c -0.2504 0.7828 -0.2256 1.6308 0.072 2.3976 l 5.0612 13.0392 h 15.7088 v -3.396 l -2.5976 -8.166 c -0.244 -0.7636 -0.2252 -1.5868 0.0532 -2.3408 l 10.5884 -28.7388 c 0.526 -1.4212 1.8776 -2.3632 3.3896 -2.3632 h 10.8044 c 0.9796 0 1.9184 0.4008 2.6008 1.1108 l 29.9252 41.346 c 0 0 15.2832 0 20.3788 0 c 5.0952 0 3.1236 -10.1656 -1.6964 -10.6168 c -9.1304 -0.848 -20.8016 -26.3192 -25.8968 -35.236 c -5.0956 -8.914 9.8656 -36.5196 21.2268 -37.7812 c 11.4616 -1.274 17.4056 -5.9468 19.1052 -12.316 C 205.5424 89.2748 205.968 80.7836 201.7208 78.236 z".into(); 32 | let cat_svg_path_drawing = generator.path::(cat_svg_path.clone()); 33 | let background_color = Color::from_hex_str("96C0B7").unwrap(); 34 | 35 | // translate given path 36 | let mut translated_path = PathTransformer::new(cat_svg_path); 37 | translated_path.translate(230.0, 0.0); 38 | let translated_path_string = translated_path.to_string(); 39 | let translated_path_bbox = translated_path.to_box(None); 40 | 41 | // change colors etc of translated path to distinguish it from original 42 | let translated_options = OptionsBuilder::default() 43 | .stroke(Srgba::from_components((114u8, 87, 82, 255u8)).into_format()) 44 | .fill(Srgba::from_components((156u8, 1, 188, 255u8)).into_format()) 45 | .fill_style(FillStyle::Hachure) 46 | .fill_weight(DPI * 0.01) 47 | .build() 48 | .unwrap(); 49 | let translated_generator = KurboGenerator::new(translated_options); 50 | let translated_halloween_path_drawing = 51 | translated_generator.path::(translated_path_string); 52 | 53 | let bbox_options = OptionsBuilder::default() 54 | .stroke(Srgba::from_components((114u8, 87, 82, 255u8)).into_format()) 55 | .fill_weight(DPI * 0.01) 56 | .build() 57 | .unwrap(); 58 | let bbox_generator = KurboGenerator::new(bbox_options); 59 | let bbox = bbox_generator.rectangle( 60 | translated_path_bbox.min_x.unwrap_or(0.0), 61 | translated_path_bbox.min_y.unwrap_or(0.0), 62 | translated_path_bbox.width(), 63 | translated_path_bbox.height(), 64 | ); 65 | 66 | rc.fill( 67 | Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64), 68 | &background_color, 69 | ); 70 | cat_svg_path_drawing.draw(&mut rc); 71 | translated_halloween_path_drawing.draw(&mut rc); 72 | bbox.draw(&mut rc); 73 | rc.finish().unwrap(); 74 | std::mem::drop(rc); 75 | 76 | bitmap 77 | .save_to_file("translated_cat.png") 78 | .expect("file save error"); 79 | } 80 | -------------------------------------------------------------------------------- /rough_piet/src/kurbo_generator.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | use std::ops::MulAssign; 3 | 4 | use euclid::default::Point2D; 5 | use euclid::Trig; 6 | use num_traits::{Float, FromPrimitive}; 7 | use palette::rgb::Rgba; 8 | use palette::Srgba; 9 | use piet::kurbo::{self, BezPath, PathEl, Point}; 10 | use piet::{Color, LineJoin, RenderContext, StrokeStyle}; 11 | use roughr::core::{Drawable, OpSet, OpSetType, OpType, Options}; 12 | use roughr::generator::Generator; 13 | use roughr::PathSegment; 14 | 15 | #[derive(Default)] 16 | pub struct KurboGenerator { 17 | gen: Generator, 18 | options: Option, 19 | } 20 | 21 | #[derive(Clone)] 22 | pub struct KurboOpset { 23 | pub op_set_type: OpSetType, 24 | pub ops: BezPath, 25 | pub size: Option>, 26 | pub path: Option, 27 | } 28 | 29 | pub trait ToKurboOpset { 30 | fn to_kurbo_opset(self) -> KurboOpset; 31 | } 32 | 33 | impl ToKurboOpset for OpSet { 34 | fn to_kurbo_opset(self) -> KurboOpset { 35 | KurboOpset { 36 | op_set_type: self.op_set_type.clone(), 37 | size: self.size, 38 | path: self.path.clone(), 39 | ops: opset_to_shape(&self), 40 | } 41 | } 42 | } 43 | 44 | pub struct KurboDrawable { 45 | pub shape: String, 46 | pub options: Options, 47 | pub sets: Vec>, 48 | } 49 | 50 | pub trait ToKurboDrawable { 51 | fn to_kurbo_drawable(self) -> KurboDrawable; 52 | } 53 | 54 | impl ToKurboDrawable for Drawable { 55 | fn to_kurbo_drawable(self) -> KurboDrawable { 56 | KurboDrawable { 57 | shape: self.shape, 58 | options: self.options, 59 | sets: self.sets.into_iter().map(|s| s.to_kurbo_opset()).collect(), 60 | } 61 | } 62 | } 63 | 64 | impl KurboGenerator { 65 | pub fn new(options: Options) -> Self { 66 | KurboGenerator { gen: Generator::default(), options: Some(options) } 67 | } 68 | } 69 | 70 | impl KurboDrawable { 71 | pub fn draw(&self, ctx: &mut impl RenderContext) { 72 | for set in self.sets.iter() { 73 | match set.op_set_type { 74 | OpSetType::Path => { 75 | ctx.save().expect("Failed to save render context"); 76 | if self.options.stroke_line_dash.is_some() { 77 | let stroke_line_dash = 78 | self.options.stroke_line_dash.clone().unwrap_or(Vec::new()); 79 | let mut ss = StrokeStyle::new(); 80 | ss.set_dash_pattern(stroke_line_dash.as_slice()); 81 | ss.set_dash_offset(self.options.stroke_line_dash_offset.unwrap_or(1.0f64)); 82 | ss.set_line_cap(convert_line_cap_from_roughr_to_piet( 83 | self.options.line_cap, 84 | )); 85 | ss.set_line_join(convert_line_join_from_roughr_to_piet( 86 | self.options.line_join, 87 | )); 88 | 89 | let stroke_color = self 90 | .options 91 | .stroke 92 | .unwrap_or_else(|| Srgba::from_components((1.0, 1.0, 1.0, 1.0))); 93 | let rgb: (f32, f32, f32, f32) = stroke_color.into_components(); 94 | ctx.stroke_styled( 95 | set.ops.clone(), 96 | &Color::rgba(rgb.0 as f64, rgb.1 as f64, rgb.2 as f64, rgb.3 as f64), 97 | self.options.stroke_width.unwrap_or(1.0) as f64, 98 | &ss, 99 | ); 100 | ctx.restore().expect("Failed to restore render context"); 101 | } else { 102 | let stroke_color = self 103 | .options 104 | .stroke 105 | .unwrap_or_else(|| Srgba::new(1.0, 1.0, 1.0, 1.0)); 106 | let rgb: (f32, f32, f32, f32) = stroke_color.into_components(); 107 | ctx.stroke( 108 | set.ops.clone(), 109 | &Color::rgba(rgb.0 as f64, rgb.1 as f64, rgb.2 as f64, rgb.3 as f64), 110 | self.options.stroke_width.unwrap_or(1.0) as f64, 111 | ); 112 | ctx.restore().expect("Failed to restore render context"); 113 | } 114 | } 115 | OpSetType::FillPath => { 116 | ctx.save().expect("Failed to save render context"); 117 | match self.shape.as_str() { 118 | "curve" | "polygon" | "path" => { 119 | let fill_color = 120 | self.options.fill.unwrap_or(Rgba::new(1.0, 1.0, 1.0, 1.0)); 121 | let rgb: (f32, f32, f32, f32) = fill_color.into_components(); 122 | ctx.fill_even_odd( 123 | set.ops.clone(), 124 | &Color::rgba( 125 | rgb.0 as f64, 126 | rgb.1 as f64, 127 | rgb.2 as f64, 128 | rgb.3 as f64, 129 | ), 130 | ) 131 | } 132 | _ => { 133 | let fill_color = 134 | self.options.fill.unwrap_or(Rgba::new(1.0, 1.0, 1.0, 1.0)); 135 | let rgb: (f32, f32, f32, f32) = fill_color.into_components(); 136 | ctx.fill( 137 | set.ops.clone(), 138 | &Color::rgba( 139 | rgb.0 as f64, 140 | rgb.1 as f64, 141 | rgb.2 as f64, 142 | rgb.3 as f64, 143 | ), 144 | ) 145 | } 146 | } 147 | ctx.restore().expect("Failed to restore render context"); 148 | } 149 | OpSetType::FillSketch => { 150 | let mut fweight = self.options.fill_weight.unwrap_or_default(); 151 | if fweight < 0.0 { 152 | fweight = self.options.stroke_width.unwrap_or(1.0) / 2.0; 153 | } 154 | ctx.save().expect("Failed to save render context"); 155 | 156 | if self.options.fill_line_dash.is_some() { 157 | let fill_line_dash = 158 | self.options.fill_line_dash.clone().unwrap_or_default(); 159 | let mut ss = StrokeStyle::new(); 160 | ss.set_dash_pattern(fill_line_dash.as_slice()); 161 | ss.set_dash_offset(self.options.fill_line_dash_offset.unwrap_or(0.0f64)); 162 | ss.set_line_cap(convert_line_cap_from_roughr_to_piet( 163 | self.options.line_cap, 164 | )); 165 | ss.set_line_join(convert_line_join_from_roughr_to_piet( 166 | self.options.line_join, 167 | )); 168 | let fill_color = self 169 | .options 170 | .fill 171 | .unwrap_or_else(|| Rgba::new(1.0, 1.0, 1.0, 1.0)); 172 | let rgb: (f32, f32, f32, f32) = fill_color.into_components(); 173 | ctx.stroke_styled( 174 | set.ops.clone(), 175 | &Color::rgba(rgb.0 as f64, rgb.1 as f64, rgb.2 as f64, rgb.3 as f64), 176 | fweight as f64, 177 | &ss, 178 | ); 179 | } else { 180 | let fill_color = self 181 | .options 182 | .fill 183 | .unwrap_or_else(|| Rgba::new(1.0, 1.0, 1.0, 1.0)); 184 | let rgb: (f32, f32, f32, f32) = fill_color.into_components(); 185 | ctx.stroke( 186 | set.ops.clone(), 187 | &Color::rgba(rgb.0 as f64, rgb.1 as f64, rgb.2 as f64, rgb.3 as f64), 188 | fweight as f64, 189 | ); 190 | } 191 | ctx.restore().expect("Failed to restore render context"); 192 | } 193 | } 194 | } 195 | } 196 | } 197 | 198 | fn opset_to_shape(op_set: &OpSet) -> BezPath { 199 | let mut path: BezPath = BezPath::new(); 200 | for item in op_set.ops.iter() { 201 | match item.op { 202 | OpType::Move => path.extend([PathEl::MoveTo(Point::new( 203 | item.data[0].to_f64().unwrap(), 204 | item.data[1].to_f64().unwrap(), 205 | ))]), 206 | OpType::BCurveTo => path.extend([PathEl::CurveTo( 207 | Point::new( 208 | item.data[0].to_f64().unwrap(), 209 | item.data[1].to_f64().unwrap(), 210 | ), 211 | Point::new( 212 | item.data[2].to_f64().unwrap(), 213 | item.data[3].to_f64().unwrap(), 214 | ), 215 | Point::new( 216 | item.data[4].to_f64().unwrap(), 217 | item.data[5].to_f64().unwrap(), 218 | ), 219 | )]), 220 | OpType::LineTo => { 221 | path.extend([PathEl::LineTo(Point::new( 222 | item.data[0].to_f64().unwrap(), 223 | item.data[1].to_f64().unwrap(), 224 | ))]); 225 | } 226 | } 227 | } 228 | path 229 | } 230 | 231 | impl KurboGenerator { 232 | pub fn line( 233 | &self, 234 | x1: F, 235 | y1: F, 236 | x2: F, 237 | y2: F, 238 | ) -> KurboDrawable { 239 | let drawable = self.gen.line(x1, y1, x2, y2, &self.options); 240 | drawable.to_kurbo_drawable() 241 | } 242 | 243 | pub fn rectangle( 244 | &self, 245 | x: F, 246 | y: F, 247 | width: F, 248 | height: F, 249 | ) -> KurboDrawable { 250 | let drawable = self.gen.rectangle(x, y, width, height, &self.options); 251 | drawable.to_kurbo_drawable() 252 | } 253 | 254 | pub fn ellipse( 255 | &self, 256 | x: F, 257 | y: F, 258 | width: F, 259 | height: F, 260 | ) -> KurboDrawable { 261 | let drawable = self.gen.ellipse(x, y, width, height, &self.options); 262 | drawable.to_kurbo_drawable() 263 | } 264 | 265 | pub fn circle( 266 | &self, 267 | x: F, 268 | y: F, 269 | diameter: F, 270 | ) -> KurboDrawable { 271 | let drawable = self.gen.circle(x, y, diameter, &self.options); 272 | drawable.to_kurbo_drawable() 273 | } 274 | 275 | pub fn linear_path( 276 | &self, 277 | points: &[Point2D], 278 | close: bool, 279 | ) -> KurboDrawable { 280 | let drawable = self.gen.linear_path(points, close, &self.options); 281 | drawable.to_kurbo_drawable() 282 | } 283 | 284 | pub fn polygon( 285 | &self, 286 | points: &[Point2D], 287 | ) -> KurboDrawable { 288 | let drawable = self.gen.polygon(points, &self.options); 289 | drawable.to_kurbo_drawable() 290 | } 291 | 292 | pub fn arc( 293 | &self, 294 | x: F, 295 | y: F, 296 | width: F, 297 | height: F, 298 | start: F, 299 | stop: F, 300 | closed: bool, 301 | ) -> KurboDrawable { 302 | let drawable = self 303 | .gen 304 | .arc(x, y, width, height, start, stop, closed, &self.options); 305 | drawable.to_kurbo_drawable() 306 | } 307 | 308 | pub fn bezier_quadratic( 309 | &self, 310 | start: Point2D, 311 | cp: Point2D, 312 | end: Point2D, 313 | ) -> KurboDrawable { 314 | let drawable = self.gen.bezier_quadratic(start, cp, end, &self.options); 315 | drawable.to_kurbo_drawable() 316 | } 317 | 318 | pub fn bezier_cubic( 319 | &self, 320 | start: Point2D, 321 | cp1: Point2D, 322 | cp2: Point2D, 323 | end: Point2D, 324 | ) -> KurboDrawable { 325 | let drawable = self.gen.bezier_cubic(start, cp1, cp2, end, &self.options); 326 | drawable.to_kurbo_drawable() 327 | } 328 | 329 | pub fn curve( 330 | &self, 331 | points: &[Point2D], 332 | ) -> KurboDrawable { 333 | let drawable = self.gen.curve(points, &self.options); 334 | drawable.to_kurbo_drawable() 335 | } 336 | 337 | pub fn path( 338 | &self, 339 | svg_path: String, 340 | ) -> KurboDrawable { 341 | let drawable = self.gen.path(svg_path, &self.options); 342 | drawable.to_kurbo_drawable() 343 | } 344 | 345 | pub fn bez_path( 346 | &self, 347 | bezier_path: BezPath, 348 | ) -> KurboDrawable { 349 | let segments = bezpath_to_svg_segments(&bezier_path); 350 | self.gen 351 | .path_from_segments(segments, &self.options) 352 | .to_kurbo_drawable() 353 | } 354 | } 355 | 356 | fn convert_line_cap_from_roughr_to_piet( 357 | roughr_line_cap: Option, 358 | ) -> piet::LineCap { 359 | match roughr_line_cap { 360 | Some(roughr::core::LineCap::Butt) => piet::LineCap::Butt, 361 | Some(roughr::core::LineCap::Round) => piet::LineCap::Round, 362 | Some(roughr::core::LineCap::Square) => piet::LineCap::Square, 363 | None => piet::LineCap::Butt, 364 | } 365 | } 366 | 367 | fn convert_line_join_from_roughr_to_piet( 368 | roughr_line_join: Option, 369 | ) -> LineJoin { 370 | match roughr_line_join { 371 | Some(roughr::core::LineJoin::Miter { limit }) => LineJoin::Miter { limit }, 372 | Some(roughr::core::LineJoin::Round) => LineJoin::Round, 373 | Some(roughr::core::LineJoin::Bevel) => LineJoin::Bevel, 374 | None => LineJoin::Miter { limit: LineJoin::DEFAULT_MITER_LIMIT }, 375 | } 376 | } 377 | 378 | pub fn bezpath_to_svg_segments(path: &BezPath) -> Vec { 379 | let mut segments = Vec::new(); 380 | 381 | for elem in path.elements() { 382 | match elem { 383 | kurbo::PathEl::MoveTo(p) => { 384 | segments.push(PathSegment::MoveTo { abs: true, x: p.x, y: p.y }); 385 | } 386 | kurbo::PathEl::LineTo(p) => { 387 | segments.push(PathSegment::LineTo { abs: true, x: p.x, y: p.y }); 388 | } 389 | kurbo::PathEl::QuadTo(p1, p2) => { 390 | segments.push(PathSegment::Quadratic { 391 | abs: true, 392 | x1: p1.x, 393 | y1: p1.y, 394 | x: p2.x, 395 | y: p2.y, 396 | }); 397 | } 398 | kurbo::PathEl::CurveTo(p1, p2, p3) => { 399 | segments.push(PathSegment::CurveTo { 400 | abs: true, 401 | x1: p1.x, 402 | y1: p1.y, 403 | x2: p2.x, 404 | y2: p2.y, 405 | x: p3.x, 406 | y: p3.y, 407 | }); 408 | } 409 | kurbo::PathEl::ClosePath => { 410 | segments.push(PathSegment::ClosePath { abs: true }); 411 | } 412 | } 413 | } 414 | 415 | segments 416 | } 417 | -------------------------------------------------------------------------------- /rough_piet/src/lib.rs: -------------------------------------------------------------------------------- 1 | // This crate is entirely safe 2 | #![forbid(unsafe_code)] 3 | // Ensures that `pub` means published in the public API. 4 | // This property is useful for reasoning about breaking API changes. 5 | #![deny(unreachable_pub)] 6 | 7 | //! 8 | //! This crate is an adapter crate between [roughr](https://github.com/orhanbalci/rough-rs/main/roughr) and 9 | //! [piet](https://github.com/linebender/piet) crates. Converts from roughr drawing 10 | //! primitives to piets path types. Also has convenience traits for drawing onto piet contexts. For more detailed 11 | //! information you can check roughr crate. 12 | //! 13 | //! Below examples are output of [rough_piet](https://github.com/orhanbalci/rough-rs/tree/main/rough_piet) adapter. 14 | //! 15 | //! ## 📦 Cargo.toml 16 | //! 17 | //! ```toml 18 | //! [dependencies] 19 | //! rough_piet = "0.1" 20 | //! ``` 21 | //! 22 | //! ## 🔧 Example 23 | //! 24 | //! ### Rectangle 25 | //! 26 | //! ```ignore 27 | //! let options = OptionsBuilder::default() 28 | //! .stroke(Srgba::from_raw(&[114u8, 87u8, 82u8, 255u8]).into_format()) 29 | //! .fill(Srgba::from_raw(&[254u8, 246u8, 201u8, 255u8]).into_format()) 30 | //! .fill_style(FillStyle::Hachure) 31 | //! .fill_weight(DPI * 0.01) 32 | //! .build() 33 | //! .unwrap(); 34 | //! let generator = KurboGenerator::new(options); 35 | //! let rect_width = 100.0; 36 | //! let rect_height = 50.0; 37 | //! let rect = generator.rectangle::( 38 | //! (WIDTH as f32 - rect_width) / 2.0, 39 | //! (HEIGHT as f32 - rect_height) / 2.0, 40 | //! rect_width, 41 | //! rect_height, 42 | //! ); 43 | //! let background_color = Color::from_hex_str("96C0B7").unwrap(); 44 | //! 45 | //! rc.fill( 46 | //! Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64), 47 | //! &background_color, 48 | //! ); 49 | //! rect.draw(&mut rc); 50 | //! ``` 51 | //! 52 | //! ### 🖨️ Output Rectangle 53 | //! ![rectangle](https://raw.githubusercontent.com/orhanbalci/rough-rs/main/roughr/assets/rectangle.png) 54 | //! 55 | //! ### Circle 56 | //! 57 | //! ```ignore 58 | //! let options = OptionsBuilder::default() 59 | //! .stroke(Srgba::from_raw(&[114u8, 87u8, 82u8, 255u8]).into_format()) 60 | //! .fill(Srgba::from_raw(&[254u8, 246u8, 201u8, 255u8]).into_format()) 61 | //! .fill_style(FillStyle::Hachure) 62 | //! .fill_weight(DPI * 0.01) 63 | //! .build() 64 | //! .unwrap(); 65 | //! let generator = KurboGenerator::new(options); 66 | //! let circle_paths = generator.circle::( 67 | //! (WIDTH as f32) / 2.0, 68 | //! (HEIGHT as f32) / 2.0, 69 | //! HEIGHT as f32 - 10.0f32, 70 | //! ); 71 | //! let background_color = Color::from_hex_str("96C0B7").unwrap(); 72 | //! 73 | //! rc.fill( 74 | //! Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64), 75 | //! &background_color, 76 | //! ); 77 | //! circle_paths.draw(&mut rc); 78 | //! ``` 79 | //! 80 | //! ### 🖨️ Output Circle 81 | //! ![circle](https://raw.githubusercontent.com/orhanbalci/rough-rs/main/roughr/assets/circle.png) 82 | //! 83 | //! 84 | //! ### Ellipse 85 | //! 86 | //! ```ignore 87 | //! let options = OptionsBuilder::default() 88 | //! .stroke(Srgba::from_raw(&[114u8, 87u8, 82u8, 255u8]).into_format()) 89 | //! .fill(Srgba::from_raw(&[254u8, 246u8, 201u8, 255u8]).into_format()) 90 | //! .fill_style(FillStyle::Hachure) 91 | //! .fill_weight(DPI * 0.01) 92 | //! .build() 93 | //! .unwrap(); 94 | //! let generator = KurboGenerator::new(options); 95 | //! let ellipse_paths = generator.ellipse::( 96 | //! (WIDTH as f32) / 2.0, 97 | //! (HEIGHT as f32) / 2.0, 98 | //! WIDTH as f32 - 10.0, 99 | //! HEIGHT as f32 - 10.0, 100 | //! ); 101 | //! let background_color = Color::from_hex_str("96C0B7").unwrap(); 102 | //! 103 | //! rc.fill( 104 | //! Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64), 105 | //! &background_color, 106 | //! ); 107 | //! ellipse_paths.draw(&mut rc); 108 | //! ``` 109 | //! 110 | //! ### 🖨️ Output Ellipse 111 | //! ![ellipse](https://raw.githubusercontent.com/orhanbalci/rough-rs/main/roughr/assets/ellipse.png) 112 | //! 113 | //! 114 | //! ### Svg Path 115 | //! 116 | //! ```ignore 117 | //! let options = OptionsBuilder::default() 118 | //! .stroke(Srgba::from_raw(&[114u8, 87u8, 82u8, 255u8]).into_format()) 119 | //! .fill(Srgba::from_raw(&[254u8, 246u8, 201u8, 255u8]).into_format()) 120 | //! .fill_style(FillStyle::Hachure) 121 | //! .fill_weight(DPI * 0.01) 122 | //! .build() 123 | //! .unwrap(); 124 | //! let generator = KurboGenerator::new(options); 125 | //! let heart_svg_path = "M140 20C73 20 20 74 20 140c0 135 136 170 228 303 88-132 229-173 229-303 0-66-54-120-120-120-48 0-90 28-109 69-19-41-60-69-108-69z".into(); 126 | //! let heart_svg_path_drawing = generator.path::(heart_svg_path); 127 | //! let background_color = Color::from_hex_str("96C0B7").unwrap(); 128 | //! 129 | //! rc.fill( 130 | //! Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64), 131 | //! &background_color, 132 | //! ); 133 | //! heart_svg_path_drawing.draw(&mut rc); 134 | //! ``` 135 | //! 136 | //! ### 🖨️ Output Svg Path 137 | //! ![svgheart](https://raw.githubusercontent.com/orhanbalci/rough-rs/main/roughr/assets/heart_svg_path.png) 138 | //! 139 | //! ## Filler Implementation Status 140 | //! - [x] Hachure 141 | //! - [x] Zigzag 142 | //! - [x] Cross-Hatch 143 | //! - [x] Dots 144 | //! - [x] Dashed 145 | //! - [x] Zigzag-Line 146 | //! 147 | //! ## 🔭 Examples 148 | //! 149 | //! For more examples have a look at the 150 | //! [examples](https://github.com/orhanbalci/rough-rs/tree/main/rough_piet/examples) folder. 151 | 152 | pub mod kurbo_generator; 153 | pub use kurbo_generator::*; 154 | -------------------------------------------------------------------------------- /rough_tiny_skia/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rough_tiny_skia" 3 | version = "0.11.0" 4 | edition = "2021" 5 | authors = ["orhanbalci@gmail.com "] 6 | description = "Draw Hand Sketched 2D Drawings Using tiny-skia" 7 | repository = "https://github.com/orhanbalci/rough-rs.git" 8 | homepage = "https://github.com/orhanbalci" 9 | keywords = ["graphics", "bezier", "sketch", "2D", "piet"] 10 | categories = ["graphics"] 11 | license = "MIT" 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | roughr = {path = "../roughr", version = "0.11.0"} 16 | num-traits = "0.2" 17 | euclid = "0.22" 18 | palette = "0.7" 19 | tiny-skia = "0.11" 20 | -------------------------------------------------------------------------------- /rough_tiny_skia/examples/rectangle.rs: -------------------------------------------------------------------------------- 1 | use palette::Srgba; 2 | use rough_tiny_skia::SkiaGenerator; 3 | use roughr::core::{FillStyle, OptionsBuilder}; 4 | use tiny_skia::*; 5 | 6 | const WIDTH: f32 = 192.0; 7 | const HEIGHT: f32 = 108.0; 8 | /// For now, assume pixel density (dots per inch) 9 | const DPI: f32 = 96.; 10 | 11 | fn main() { 12 | let options = OptionsBuilder::default() 13 | .stroke(Srgba::from_components((114u8, 87u8, 82u8, 255u8)).into_format()) 14 | .fill(Srgba::from_components((254u8, 246u8, 201u8, 255u8)).into_format()) 15 | .fill_style(FillStyle::Hachure) 16 | .fill_weight(DPI * 0.01) 17 | .build() 18 | .unwrap(); 19 | let generator = SkiaGenerator::new(options); 20 | let rect_width = 100.0; 21 | let rect_height = 50.0; 22 | let rect = generator.rectangle::( 23 | (WIDTH - rect_width) / 2.0, 24 | (HEIGHT - rect_height) / 2.0, 25 | rect_width, 26 | rect_height, 27 | ); 28 | 29 | let mut pixmap = Pixmap::new(WIDTH as u32, HEIGHT as u32).unwrap(); 30 | let mut background_paint = Paint::default(); 31 | background_paint.set_color_rgba8(150, 192, 183, 200); 32 | 33 | pixmap.fill_rect( 34 | Rect::from_xywh(0.0, 0.0, WIDTH, HEIGHT).unwrap(), 35 | &background_paint, 36 | Transform::identity(), 37 | None, 38 | ); 39 | 40 | rect.draw(&mut pixmap.as_mut()); 41 | 42 | pixmap.save_png("skia_rectangle.png").unwrap(); 43 | } 44 | -------------------------------------------------------------------------------- /rough_tiny_skia/examples/rectangle_alpha.rs: -------------------------------------------------------------------------------- 1 | use palette::Srgba; 2 | use rough_tiny_skia::SkiaGenerator; 3 | use roughr::core::{FillStyle, OptionsBuilder}; 4 | use tiny_skia::*; 5 | 6 | const WIDTH: f32 = 192.0; 7 | const HEIGHT: f32 = 108.0; 8 | /// For now, assume pixel density (dots per inch) 9 | const DPI: f32 = 96.; 10 | 11 | fn main() { 12 | let options = OptionsBuilder::default() 13 | .stroke(Srgba::from_components((114u8, 87u8, 82u8, 100u8)).into_format()) 14 | .fill(Srgba::from_components((254u8, 246u8, 201u8, 100u8)).into_format()) 15 | .fill_style(FillStyle::Hachure) 16 | .fill_weight(DPI * 0.01) 17 | .build() 18 | .unwrap(); 19 | let generator = SkiaGenerator::new(options); 20 | let rect_width = 100.0; 21 | let rect_height = 50.0; 22 | let rect = generator.rectangle::( 23 | (WIDTH - rect_width) / 2.0, 24 | (HEIGHT - rect_height) / 2.0, 25 | rect_width, 26 | rect_height, 27 | ); 28 | 29 | let mut pixmap = Pixmap::new(WIDTH as u32, HEIGHT as u32).unwrap(); 30 | let mut background_paint = Paint::default(); 31 | background_paint.set_color_rgba8(150, 192, 183, 255); 32 | 33 | pixmap.fill_rect( 34 | Rect::from_xywh(0.0, 0.0, WIDTH, HEIGHT).unwrap(), 35 | &background_paint, 36 | Transform::identity(), 37 | None, 38 | ); 39 | 40 | rect.draw(&mut pixmap.as_mut()); 41 | 42 | pixmap.save_png("skia_rectangle_alpha.png").unwrap(); 43 | } 44 | -------------------------------------------------------------------------------- /rough_tiny_skia/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod skia_generator; 2 | pub use skia_generator::*; 3 | -------------------------------------------------------------------------------- /rough_tiny_skia/src/skia_generator.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | use std::ops::MulAssign; 3 | 4 | use euclid::default::Point2D; 5 | use euclid::Trig; 6 | use num_traits::{Float, FromPrimitive}; 7 | use palette::Srgba; 8 | use roughr::core::{Drawable, OpSet, OpSetType, OpType, Options}; 9 | use roughr::generator::Generator; 10 | use tiny_skia::{ 11 | FillRule, 12 | LineCap, 13 | LineJoin, 14 | Paint, 15 | Path, 16 | PathBuilder, 17 | PixmapMut, 18 | Stroke, 19 | StrokeDash, 20 | Transform, 21 | }; 22 | 23 | #[derive(Default)] 24 | pub struct SkiaGenerator { 25 | gen: Generator, 26 | options: Option, 27 | } 28 | 29 | #[derive(Clone)] 30 | pub struct SkiaOpset { 31 | pub op_set_type: OpSetType, 32 | pub ops: Path, 33 | pub size: Option>, 34 | pub path: Option, 35 | } 36 | 37 | pub trait ToSkiaOpset { 38 | fn to_skia_opset(self) -> SkiaOpset; 39 | } 40 | 41 | impl ToSkiaOpset for OpSet { 42 | fn to_skia_opset(self) -> SkiaOpset { 43 | SkiaOpset { 44 | op_set_type: self.op_set_type.clone(), 45 | size: self.size, 46 | path: self.path.clone(), 47 | ops: opset_to_shape(&self), 48 | } 49 | } 50 | } 51 | 52 | pub struct SkiaDrawable { 53 | pub shape: String, 54 | pub options: Options, 55 | pub sets: Vec>, 56 | } 57 | 58 | pub trait ToSkiaDrawable { 59 | fn to_skia_drawable(self) -> SkiaDrawable; 60 | } 61 | 62 | impl ToSkiaDrawable for Drawable { 63 | fn to_skia_drawable(self) -> SkiaDrawable { 64 | SkiaDrawable { 65 | shape: self.shape, 66 | options: self.options, 67 | sets: self.sets.into_iter().map(|s| s.to_skia_opset()).collect(), 68 | } 69 | } 70 | } 71 | 72 | impl SkiaGenerator { 73 | pub fn new(options: Options) -> Self { 74 | SkiaGenerator { gen: Generator::default(), options: Some(options) } 75 | } 76 | } 77 | 78 | impl SkiaDrawable { 79 | pub fn draw(&self, ctx: &mut PixmapMut) { 80 | for set in self.sets.iter() { 81 | match set.op_set_type { 82 | OpSetType::Path => { 83 | if self.options.stroke_line_dash.is_some() { 84 | let mut stroke = Stroke { 85 | width: self.options.stroke_width.unwrap_or(1.0), 86 | line_cap: convert_line_cap_from_roughr_to_piet(self.options.line_cap), 87 | line_join: convert_line_join_from_roughr_to_piet( 88 | self.options.line_join, 89 | ), 90 | ..Stroke::default() 91 | }; 92 | let stroke_line_dash = self 93 | .options 94 | .stroke_line_dash 95 | .clone() 96 | .unwrap_or(Vec::new()) 97 | .iter() 98 | .map(|&a| a as f32) 99 | .collect(); 100 | 101 | stroke.dash = StrokeDash::new( 102 | stroke_line_dash, 103 | self.options.stroke_line_dash_offset.unwrap_or(1.0f64) as f32, 104 | ); 105 | 106 | let stroke_color = self 107 | .options 108 | .stroke 109 | .unwrap_or_else(|| Srgba::from_components((1.0, 1.0, 1.0, 1.0))); 110 | let stroke_color_components: (u8, u8, u8, u8) = 111 | stroke_color.into_format().into_components(); 112 | 113 | let mut paint = Paint::default(); 114 | paint.set_color_rgba8( 115 | stroke_color_components.0, 116 | stroke_color_components.1, 117 | stroke_color_components.2, 118 | stroke_color_components.3, 119 | ); 120 | paint.anti_alias = true; 121 | 122 | ctx.stroke_path(&set.ops, &paint, &stroke, Transform::identity(), None); 123 | } else { 124 | let mut stroke = Stroke::default(); 125 | stroke.width = self.options.stroke_width.unwrap_or(1.0); 126 | 127 | let stroke_color = self 128 | .options 129 | .stroke 130 | .unwrap_or_else(|| Srgba::from_components((1.0, 1.0, 1.0, 1.0))); 131 | let stroke_color_components: (u8, u8, u8, u8) = 132 | stroke_color.into_format().into_components(); 133 | 134 | let mut paint = Paint::default(); 135 | paint.set_color_rgba8( 136 | stroke_color_components.0, 137 | stroke_color_components.1, 138 | stroke_color_components.2, 139 | stroke_color_components.3, 140 | ); 141 | paint.anti_alias = true; 142 | 143 | ctx.stroke_path(&set.ops, &paint, &stroke, Transform::identity(), None); 144 | } 145 | } 146 | OpSetType::FillPath => { 147 | let fill_color = self 148 | .options 149 | .fill 150 | .unwrap_or(Srgba::from_components((1.0, 1.0, 1.0, 1.0))); 151 | let fill_color_components: (u8, u8, u8, u8) = 152 | fill_color.into_format().into_components(); 153 | 154 | let mut paint = Paint::default(); 155 | paint.set_color_rgba8( 156 | fill_color_components.0, 157 | fill_color_components.1, 158 | fill_color_components.2, 159 | fill_color_components.3, 160 | ); 161 | paint.anti_alias = true; 162 | match self.shape.as_str() { 163 | "curve" | "polygon" | "path" => { 164 | ctx.fill_path( 165 | &set.ops, 166 | &paint, 167 | FillRule::EvenOdd, 168 | Transform::identity(), 169 | None, 170 | ); 171 | } 172 | _ => { 173 | ctx.fill_path( 174 | &set.ops, 175 | &paint, 176 | FillRule::Winding, 177 | Transform::identity(), 178 | None, 179 | ); 180 | } 181 | } 182 | } 183 | OpSetType::FillSketch => { 184 | let mut fweight = self.options.fill_weight.unwrap_or_default(); 185 | if fweight < 0.0 { 186 | fweight = self.options.stroke_width.unwrap_or(1.0) / 2.0; 187 | } 188 | 189 | if self.options.fill_line_dash.is_some() { 190 | let mut stroke = Stroke::default(); 191 | stroke.width = self.options.fill_weight.unwrap_or(1.0); 192 | stroke.line_cap = 193 | convert_line_cap_from_roughr_to_piet(self.options.line_cap); 194 | stroke.line_join = 195 | convert_line_join_from_roughr_to_piet(self.options.line_join); 196 | let fill_line_dash = self 197 | .options 198 | .fill_line_dash 199 | .clone() 200 | .unwrap_or(Vec::new()) 201 | .iter() 202 | .map(|&a| a as f32) 203 | .collect(); 204 | 205 | stroke.dash = StrokeDash::new( 206 | fill_line_dash, 207 | self.options.fill_line_dash_offset.unwrap_or(1.0f64) as f32, 208 | ); 209 | 210 | let fill_color = self 211 | .options 212 | .fill 213 | .unwrap_or(Srgba::from_components((1.0, 1.0, 1.0, 1.0))); 214 | let fill_color_components: (u8, u8, u8, u8) = 215 | fill_color.into_format().into_components(); 216 | 217 | let mut paint = Paint::default(); 218 | paint.set_color_rgba8( 219 | fill_color_components.0, 220 | fill_color_components.1, 221 | fill_color_components.2, 222 | fill_color_components.3, 223 | ); 224 | paint.anti_alias = true; 225 | ctx.stroke_path(&set.ops, &paint, &stroke, Transform::identity(), None); 226 | } else { 227 | let mut stroke = Stroke::default(); 228 | stroke.width = self.options.fill_weight.unwrap_or(1.0); 229 | stroke.line_cap = 230 | convert_line_cap_from_roughr_to_piet(self.options.line_cap); 231 | stroke.line_join = 232 | convert_line_join_from_roughr_to_piet(self.options.line_join); 233 | 234 | let fill_color = self 235 | .options 236 | .fill 237 | .unwrap_or(Srgba::from_components((1.0, 1.0, 1.0, 1.0))); 238 | let fill_color_components: (u8, u8, u8, u8) = 239 | fill_color.into_format().into_components(); 240 | 241 | let mut paint = Paint::default(); 242 | paint.set_color_rgba8( 243 | fill_color_components.0, 244 | fill_color_components.1, 245 | fill_color_components.2, 246 | fill_color_components.3, 247 | ); 248 | paint.anti_alias = true; 249 | ctx.stroke_path(&set.ops, &paint, &stroke, Transform::identity(), None); 250 | } 251 | } 252 | } 253 | } 254 | } 255 | } 256 | 257 | fn opset_to_shape(op_set: &OpSet) -> Path { 258 | let mut path: PathBuilder = PathBuilder::new(); 259 | for item in op_set.ops.iter() { 260 | match item.op { 261 | OpType::Move => path.move_to( 262 | item.data[0].to_f32().unwrap(), 263 | item.data[1].to_f32().unwrap(), 264 | ), 265 | OpType::BCurveTo => path.cubic_to( 266 | item.data[0].to_f32().unwrap(), 267 | item.data[1].to_f32().unwrap(), 268 | item.data[2].to_f32().unwrap(), 269 | item.data[3].to_f32().unwrap(), 270 | item.data[4].to_f32().unwrap(), 271 | item.data[5].to_f32().unwrap(), 272 | ), 273 | OpType::LineTo => { 274 | path.line_to( 275 | item.data[0].to_f32().unwrap(), 276 | item.data[1].to_f32().unwrap(), 277 | ); 278 | } 279 | } 280 | } 281 | path.finish().unwrap() 282 | } 283 | 284 | impl SkiaGenerator { 285 | pub fn line( 286 | &self, 287 | x1: F, 288 | y1: F, 289 | x2: F, 290 | y2: F, 291 | ) -> SkiaDrawable { 292 | let drawable = self.gen.line(x1, y1, x2, y2, &self.options); 293 | drawable.to_skia_drawable() 294 | } 295 | 296 | pub fn rectangle( 297 | &self, 298 | x: F, 299 | y: F, 300 | width: F, 301 | height: F, 302 | ) -> SkiaDrawable { 303 | let drawable = self.gen.rectangle(x, y, width, height, &self.options); 304 | drawable.to_skia_drawable() 305 | } 306 | 307 | pub fn ellipse( 308 | &self, 309 | x: F, 310 | y: F, 311 | width: F, 312 | height: F, 313 | ) -> SkiaDrawable { 314 | let drawable = self.gen.ellipse(x, y, width, height, &self.options); 315 | drawable.to_skia_drawable() 316 | } 317 | 318 | pub fn circle( 319 | &self, 320 | x: F, 321 | y: F, 322 | diameter: F, 323 | ) -> SkiaDrawable { 324 | let drawable = self.gen.circle(x, y, diameter, &self.options); 325 | drawable.to_skia_drawable() 326 | } 327 | 328 | pub fn linear_path( 329 | &self, 330 | points: &[Point2D], 331 | close: bool, 332 | ) -> SkiaDrawable { 333 | let drawable = self.gen.linear_path(points, close, &self.options); 334 | drawable.to_skia_drawable() 335 | } 336 | 337 | pub fn polygon( 338 | &self, 339 | points: &[Point2D], 340 | ) -> SkiaDrawable { 341 | let drawable = self.gen.polygon(points, &self.options); 342 | drawable.to_skia_drawable() 343 | } 344 | 345 | pub fn arc( 346 | &self, 347 | x: F, 348 | y: F, 349 | width: F, 350 | height: F, 351 | start: F, 352 | stop: F, 353 | closed: bool, 354 | ) -> SkiaDrawable { 355 | let drawable = self 356 | .gen 357 | .arc(x, y, width, height, start, stop, closed, &self.options); 358 | drawable.to_skia_drawable() 359 | } 360 | 361 | pub fn bezier_quadratic( 362 | &self, 363 | start: Point2D, 364 | cp: Point2D, 365 | end: Point2D, 366 | ) -> SkiaDrawable { 367 | let drawable = self.gen.bezier_quadratic(start, cp, end, &self.options); 368 | drawable.to_skia_drawable() 369 | } 370 | 371 | pub fn bezier_cubic( 372 | &self, 373 | start: Point2D, 374 | cp1: Point2D, 375 | cp2: Point2D, 376 | end: Point2D, 377 | ) -> SkiaDrawable { 378 | let drawable = self.gen.bezier_cubic(start, cp1, cp2, end, &self.options); 379 | drawable.to_skia_drawable() 380 | } 381 | 382 | pub fn curve( 383 | &self, 384 | points: &[Point2D], 385 | ) -> SkiaDrawable { 386 | let drawable = self.gen.curve(points, &self.options); 387 | drawable.to_skia_drawable() 388 | } 389 | 390 | pub fn path( 391 | &self, 392 | svg_path: String, 393 | ) -> SkiaDrawable { 394 | let drawable = self.gen.path(svg_path, &self.options); 395 | drawable.to_skia_drawable() 396 | } 397 | } 398 | 399 | fn convert_line_cap_from_roughr_to_piet(roughr_line_cap: Option) -> LineCap { 400 | match roughr_line_cap { 401 | Some(roughr::core::LineCap::Butt) => LineCap::Butt, 402 | Some(roughr::core::LineCap::Round) => LineCap::Round, 403 | Some(roughr::core::LineCap::Square) => LineCap::Square, 404 | None => LineCap::Round, 405 | } 406 | } 407 | 408 | fn convert_line_join_from_roughr_to_piet( 409 | roughr_line_join: Option, 410 | ) -> LineJoin { 411 | match roughr_line_join { 412 | Some(roughr::core::LineJoin::Miter { limit: _ }) => LineJoin::Miter, 413 | Some(roughr::core::LineJoin::Round) => LineJoin::Round, 414 | Some(roughr::core::LineJoin::Bevel) => LineJoin::Bevel, 415 | None => LineJoin::Miter, 416 | } 417 | } 418 | -------------------------------------------------------------------------------- /roughr/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /roughr/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "roughr" 3 | version = "0.11.0" 4 | edition = "2021" 5 | authors = ["orhanbalci@gmail.com "] 6 | description = "Generate Hand Sketched 2D Drawings" 7 | repository = "https://github.com/orhanbalci/rough-rs.git" 8 | homepage = "https://github.com/orhanbalci" 9 | keywords = ["graphics", "bezier", "sketch", "2D"] 10 | categories = ["graphics"] 11 | license = "MIT" 12 | readme = "README.md" 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | 16 | [dependencies] 17 | points_on_curve = { path = "../points_on_curve", version = "0.7.0" } 18 | svg_path_ops = { path = "../svg_path_ops", version = "0.11.0" } 19 | euclid = "0.22" 20 | rand = "0.8" 21 | num-traits = "0.2" 22 | derive_builder = "0.12" 23 | svgtypes = "0.11" 24 | palette = "0.7" 25 | 26 | [dev-dependencies] 27 | plotlib = "0.5" 28 | -------------------------------------------------------------------------------- /roughr/README.md: -------------------------------------------------------------------------------- 1 | # roughr 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/roughr.svg)](https://crates.io/crates/roughr) 4 | [![Documentation](https://docs.rs/roughr/badge.svg)](https://docs.rs/roughr) 5 | [![License](https://img.shields.io/github/license/orhanbalci/rough-rs.svg)](https://github.com/orhanbalci/rough-rs/LICENSE) 6 | 7 | 8 | 9 | 10 | This crate is a rustlang port of [Rough.js](https://github.com/rough-stuff/rough) npm package written by 11 | [@pshihn](https://github.com/pshihn). 12 | 13 | This package exposes functions to generate rough drawing primitives which looks like hand drawn sketches. 14 | This is the core create of operations to create rough drawings. It exposes its own primitive drawing types for lines 15 | curves, arcs, polygons, circles, ellipses and even svg paths. 16 | Works on [Point2D](https://docs.rs/euclid/0.22.7/euclid/struct.Point2D.html) type from [euclid](https://github.com/servo/euclid) crate 17 | 18 | On its own this crate can not draw on any context. One needs to use existing drawing libraries such as [piet](https://github.com/linebender/piet), 19 | [raqote](https://github.com/jrmuizel/raqote), [tiny-skia](https://github.com/RazrFalcon/tiny-skia) etc in combination with 20 | roughr. In this workspace an example adapter is implemented for [piet](https://github.com/linebender/piet). Below examples are 21 | output of [rough_piet](https://github.com/orhanbalci/rough-rs/tree/main/rough_piet) adapter. 22 | 23 | ## 📦 Cargo.toml 24 | 25 | ```toml 26 | [dependencies] 27 | roughr = "0.1" 28 | ``` 29 | 30 | ## 🔧 Example 31 | 32 | ### Rectangle 33 | 34 | ```rust 35 | let options = OptionsBuilder::default() 36 | .stroke(Srgba::from_raw(&[114u8, 87u8, 82u8, 255u8]).into_format()) 37 | .fill(Srgba::from_raw(&[254u8, 246u8, 201u8, 255u8]).into_format()) 38 | .fill_style(FillStyle::Hachure) 39 | .fill_weight(DPI * 0.01) 40 | .build() 41 | .unwrap(); 42 | let generator = KurboGenerator::new(options); 43 | let rect_width = 100.0; 44 | let rect_height = 50.0; 45 | let rect = generator.rectangle::( 46 | (WIDTH as f32 - rect_width) / 2.0, 47 | (HEIGHT as f32 - rect_height) / 2.0, 48 | rect_width, 49 | rect_height, 50 | ); 51 | let background_color = Color::from_hex_str("96C0B7").unwrap(); 52 | 53 | rc.fill( 54 | Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64), 55 | &background_color, 56 | ); 57 | rect.draw(&mut rc); 58 | ``` 59 | 60 | ### 🖨️ Output Rectangle 61 | ![rectangle](https://raw.githubusercontent.com/orhanbalci/rough-rs/main/roughr/assets/rectangle.png) 62 | 63 | ### Circle 64 | 65 | ```rust 66 | let options = OptionsBuilder::default() 67 | .stroke(Srgba::from_raw(&[114u8, 87u8, 82u8, 255u8]).into_format()) 68 | .fill(Srgba::from_raw(&[254u8, 246u8, 201u8, 255u8]).into_format()) 69 | .fill_style(FillStyle::Hachure) 70 | .fill_weight(DPI * 0.01) 71 | .build() 72 | .unwrap(); 73 | let generator = KurboGenerator::new(options); 74 | let circle_paths = generator.circle::( 75 | (WIDTH as f32) / 2.0, 76 | (HEIGHT as f32) / 2.0, 77 | HEIGHT as f32 - 10.0f32, 78 | ); 79 | let background_color = Color::from_hex_str("96C0B7").unwrap(); 80 | 81 | rc.fill( 82 | Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64), 83 | &background_color, 84 | ); 85 | circle_paths.draw(&mut rc); 86 | ``` 87 | 88 | ### 🖨️ Output Circle 89 | ![circle](https://raw.githubusercontent.com/orhanbalci/rough-rs/main/roughr/assets/circle.png) 90 | 91 | 92 | ### Ellipse 93 | 94 | ```rust 95 | let options = OptionsBuilder::default() 96 | .stroke(Srgba::from_raw(&[114u8, 87u8, 82u8, 255u8]).into_format()) 97 | .fill(Srgba::from_raw(&[254u8, 246u8, 201u8, 255u8]).into_format()) 98 | .fill_style(FillStyle::Hachure) 99 | .fill_weight(DPI * 0.01) 100 | .build() 101 | .unwrap(); 102 | let generator = KurboGenerator::new(options); 103 | let ellipse_paths = generator.ellipse::( 104 | (WIDTH as f32) / 2.0, 105 | (HEIGHT as f32) / 2.0, 106 | WIDTH as f32 - 10.0, 107 | HEIGHT as f32 - 10.0, 108 | ); 109 | let background_color = Color::from_hex_str("96C0B7").unwrap(); 110 | 111 | rc.fill( 112 | Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64), 113 | &background_color, 114 | ); 115 | ellipse_paths.draw(&mut rc); 116 | ``` 117 | 118 | ### 🖨️ Output Ellipse 119 | ![ellipse](https://raw.githubusercontent.com/orhanbalci/rough-rs/main/roughr/assets/ellipse.png) 120 | 121 | 122 | ### Svg Path 123 | 124 | ```rust 125 | let options = OptionsBuilder::default() 126 | .stroke(Srgba::from_raw(&[114u8, 87u8, 82u8, 255u8]).into_format()) 127 | .fill(Srgba::from_raw(&[254u8, 246u8, 201u8, 255u8]).into_format()) 128 | .fill_style(FillStyle::Hachure) 129 | .fill_weight(DPI * 0.01) 130 | .build() 131 | .unwrap(); 132 | let generator = KurboGenerator::new(options); 133 | let heart_svg_path = "M140 20C73 20 20 74 20 140c0 135 136 170 228 303 88-132 229-173 229-303 0-66-54-120-120-120-48 0-90 28-109 69-19-41-60-69-108-69z".into(); 134 | let heart_svg_path_drawing = generator.path::(heart_svg_path); 135 | let background_color = Color::from_hex_str("96C0B7").unwrap(); 136 | 137 | rc.fill( 138 | Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64), 139 | &background_color, 140 | ); 141 | heart_svg_path_drawing.draw(&mut rc); 142 | ``` 143 | 144 | ### 🖨️ Output Svg Path 145 | ![svgheart](https://raw.githubusercontent.com/orhanbalci/rough-rs/main/roughr/assets/heart_svg_path.png) 146 | 147 | ## Filler Implementation Status 148 | - [x] Hachure 149 | - [x] Zigzag 150 | - [x] Cross-Hatch 151 | - [x] Dots 152 | - [x] Dashed 153 | - [x] Zigzag-Line 154 | 155 | ## 🔭 Examples 156 | 157 | For more examples have a look at the 158 | [examples](https://github.com/orhanbalci/rough-rs/tree/main/rough_piet/examples) folder. 159 | 160 | 161 | 162 | ## 📝 License 163 | 164 | Licensed under MIT License ([LICENSE](LICENSE)). 165 | 166 | ### 🚧 Contributions 167 | 168 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the MIT license, shall be licensed as above, without any additional terms or conditions. 169 | -------------------------------------------------------------------------------- /roughr/assets/circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhanbalci/rough-rs/9449e874ef680fdf9afd6d04ca6fe69c0df9862d/roughr/assets/circle.png -------------------------------------------------------------------------------- /roughr/assets/ellipse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhanbalci/rough-rs/9449e874ef680fdf9afd6d04ca6fe69c0df9862d/roughr/assets/ellipse.png -------------------------------------------------------------------------------- /roughr/assets/heart_svg_path.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhanbalci/rough-rs/9449e874ef680fdf9afd6d04ca6fe69c0df9862d/roughr/assets/heart_svg_path.png -------------------------------------------------------------------------------- /roughr/assets/rectangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhanbalci/rough-rs/9449e874ef680fdf9afd6d04ca6fe69c0df9862d/roughr/assets/rectangle.png -------------------------------------------------------------------------------- /roughr/assets/rough_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhanbalci/rough-rs/9449e874ef680fdf9afd6d04ca6fe69c0df9862d/roughr/assets/rough_text.png -------------------------------------------------------------------------------- /roughr/assets/rust.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhanbalci/rough-rs/9449e874ef680fdf9afd6d04ca6fe69c0df9862d/roughr/assets/rust.png -------------------------------------------------------------------------------- /roughr/src/core.rs: -------------------------------------------------------------------------------- 1 | use euclid::default::Point2D; 2 | use euclid::Trig; 3 | use num_traits::{Float, FromPrimitive}; 4 | use palette::Srgba; 5 | use rand::rngs::StdRng; 6 | use rand::{random, Rng, SeedableRng}; 7 | 8 | pub struct Space; 9 | 10 | pub struct Config { 11 | options: Option, 12 | } 13 | 14 | pub struct DrawingSurface { 15 | width: f32, 16 | height: f32, 17 | } 18 | 19 | #[derive(Clone, PartialEq, Debug)] 20 | pub enum FillStyle { 21 | Solid, 22 | Hachure, 23 | ZigZag, 24 | CrossHatch, 25 | Dots, 26 | Dashed, 27 | ZigZagLine, 28 | } 29 | 30 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 31 | pub enum LineCap { 32 | Butt, 33 | Round, 34 | Square, 35 | } 36 | 37 | impl Default for LineCap { 38 | fn default() -> Self { 39 | LineCap::Butt 40 | } 41 | } 42 | 43 | /// Options for angled joins in strokes. 44 | #[derive(Clone, Copy, PartialEq, Debug)] 45 | pub enum LineJoin { 46 | Miter { limit: f64 }, 47 | Round, 48 | Bevel, 49 | } 50 | impl LineJoin { 51 | pub const DEFAULT_MITER_LIMIT: f64 = 10.0; 52 | } 53 | impl Default for LineJoin { 54 | fn default() -> Self { 55 | LineJoin::Miter { limit: LineJoin::DEFAULT_MITER_LIMIT } 56 | } 57 | } 58 | 59 | #[derive(Clone, Builder)] 60 | #[builder(setter(strip_option))] 61 | pub struct Options { 62 | #[builder(default = "Some(2.0)")] 63 | pub max_randomness_offset: Option, 64 | #[builder(default = "Some(1.0)")] 65 | pub roughness: Option, 66 | #[builder(default = "Some(2.0)")] 67 | pub bowing: Option, 68 | #[builder(default = "Some(Srgba::new(0.0, 0.0, 0.0, 1.0))")] 69 | pub stroke: Option, 70 | #[builder(default = "Some(1.0)")] 71 | pub stroke_width: Option, 72 | #[builder(default = "Some(0.95)")] 73 | pub curve_fitting: Option, 74 | #[builder(default = "Some(0.0)")] 75 | pub curve_tightness: Option, 76 | #[builder(default = "Some(9.0)")] 77 | pub curve_step_count: Option, 78 | #[builder(default = "None")] 79 | pub fill: Option, 80 | #[builder(default = "None")] 81 | pub fill_style: Option, 82 | #[builder(default = "Some(-1.0)")] 83 | pub fill_weight: Option, 84 | #[builder(default = "Some(-41.0)")] 85 | pub hachure_angle: Option, 86 | #[builder(default = "Some(-1.0)")] 87 | pub hachure_gap: Option, 88 | #[builder(default = "Some(1.0)")] 89 | pub simplification: Option, 90 | #[builder(default = "Some(-1.0)")] 91 | pub dash_offset: Option, 92 | #[builder(default = "Some(-1.0)")] 93 | pub dash_gap: Option, 94 | #[builder(default = "Some(-1.0)")] 95 | pub zigzag_offset: Option, 96 | #[builder(default = "Some(345_u64)")] 97 | pub seed: Option, 98 | #[builder(default = "None")] 99 | pub stroke_line_dash: Option>, 100 | #[builder(default = "None")] 101 | pub stroke_line_dash_offset: Option, 102 | #[builder(default = "None")] 103 | pub line_cap: Option, 104 | #[builder(default = "None")] 105 | pub line_join: Option, 106 | #[builder(default = "None")] 107 | pub fill_line_dash: Option>, 108 | #[builder(default = "None")] 109 | pub fill_line_dash_offset: Option, 110 | #[builder(default = "Some(false)")] 111 | pub disable_multi_stroke: Option, 112 | #[builder(default = "Some(false)")] 113 | pub disable_multi_stroke_fill: Option, 114 | #[builder(default = "Some(false)")] 115 | pub preserve_vertices: Option, 116 | #[builder(default = "None")] 117 | pub fixed_decimal_place_digits: Option, 118 | #[builder(default = "None")] 119 | pub randomizer: Option, 120 | } 121 | 122 | impl Default for Options { 123 | fn default() -> Self { 124 | Options { 125 | max_randomness_offset: Some(2.0), 126 | roughness: Some(1.0), 127 | bowing: Some(2.0), 128 | stroke: Some(Srgba::new(0.0, 0.0, 0.0, 1.0)), 129 | stroke_width: Some(1.0), 130 | curve_tightness: Some(0.0), 131 | curve_fitting: Some(0.95), 132 | curve_step_count: Some(9.0), 133 | fill: None, 134 | fill_style: None, 135 | fill_weight: Some(-1.0), 136 | hachure_angle: Some(-41.0), 137 | hachure_gap: Some(-1.0), 138 | dash_offset: Some(-1.0), 139 | dash_gap: Some(-1.0), 140 | zigzag_offset: Some(-1.0), 141 | seed: Some(345_u64), 142 | disable_multi_stroke: Some(false), 143 | disable_multi_stroke_fill: Some(false), 144 | preserve_vertices: Some(false), 145 | simplification: Some(1.0), 146 | stroke_line_dash: None, 147 | stroke_line_dash_offset: None, 148 | line_cap: None, 149 | line_join: None, 150 | fill_line_dash: None, 151 | fill_line_dash_offset: None, 152 | fixed_decimal_place_digits: None, 153 | randomizer: None, 154 | } 155 | } 156 | } 157 | 158 | impl Options { 159 | pub fn random(&mut self) -> f64 { 160 | match &mut self.randomizer { 161 | Some(r) => r.gen(), 162 | None => match self.seed { 163 | Some(s) => { 164 | let rnd = self.randomizer.insert(StdRng::seed_from_u64(s)); 165 | rnd.gen() 166 | } 167 | None => { 168 | let rnd = self.randomizer.insert(StdRng::seed_from_u64(random())); 169 | rnd.gen() 170 | } 171 | }, 172 | } 173 | } 174 | 175 | pub fn set_hachure_angle(&mut self, angle: Option) -> &mut Self { 176 | self.hachure_angle = angle; 177 | self 178 | } 179 | 180 | pub fn set_hachure_gap(&mut self, gap: Option) -> &mut Self { 181 | self.hachure_gap = gap; 182 | self 183 | } 184 | } 185 | 186 | #[derive(Clone, PartialEq, Debug, Eq)] 187 | pub enum OpType { 188 | Move, 189 | BCurveTo, 190 | LineTo, 191 | } 192 | 193 | #[derive(Clone, Debug, PartialEq, Eq)] 194 | pub enum OpSetType { 195 | Path, 196 | FillPath, 197 | FillSketch, 198 | } 199 | 200 | #[derive(Clone, Debug, PartialEq, Eq)] 201 | pub struct Op { 202 | pub op: OpType, 203 | pub data: Vec, 204 | } 205 | 206 | #[derive(Clone, Debug, PartialEq, Eq)] 207 | pub struct OpSet { 208 | pub op_set_type: OpSetType, 209 | pub ops: Vec>, 210 | pub size: Option>, 211 | pub path: Option, 212 | } 213 | 214 | pub struct Drawable { 215 | pub shape: String, 216 | pub options: Options, 217 | pub sets: Vec>, 218 | } 219 | 220 | pub struct PathInfo { 221 | pub d: String, 222 | pub stroke: Option, 223 | pub stroke_width: Option, 224 | pub fill: Option, 225 | } 226 | 227 | pub fn _c(inp: f32) -> U { 228 | U::from(inp).expect("can not parse from f32") 229 | } 230 | 231 | pub fn _cc(inp: f64) -> U { 232 | U::from(inp).expect("can not parse from f64") 233 | } 234 | -------------------------------------------------------------------------------- /roughr/src/filler/dashed_filler.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::BorrowMut; 2 | use std::marker::PhantomData; 3 | 4 | use euclid::default::Point2D; 5 | use euclid::{point2, Trig}; 6 | use num_traits::{Float, FromPrimitive}; 7 | 8 | use super::scan_line_hachure::polygon_hachure_lines; 9 | use super::traits::PatternFiller; 10 | use crate::core::{OpSet, Options, _c}; 11 | use crate::geometry::Line; 12 | use crate::renderer::_double_line; 13 | 14 | pub struct DashedFiller { 15 | _phantom: PhantomData, 16 | } 17 | 18 | impl PatternFiller for DashedFiller 19 | where 20 | F: Float + Trig + FromPrimitive, 21 | P: BorrowMut>>>, 22 | { 23 | fn fill_polygons(&self, mut polygon_list: P, o: &mut Options) -> crate::core::OpSet { 24 | let lines = polygon_hachure_lines(polygon_list.borrow_mut(), o); 25 | let ops = DashedFiller::dashed_line(lines, o); 26 | OpSet { 27 | op_set_type: crate::core::OpSetType::FillSketch, 28 | ops, 29 | size: None, 30 | path: None, 31 | } 32 | } 33 | } 34 | impl<'a, F: Float + Trig + FromPrimitive> DashedFiller { 35 | pub fn new() -> Self { 36 | DashedFiller { _phantom: PhantomData } 37 | } 38 | 39 | fn dashed_line(lines: Vec>, o: &mut Options) -> Vec> { 40 | let dash_offset: F = o.dash_offset.map(_c).unwrap_or_else(|| _c(-1.0)); 41 | let offset = if dash_offset < _c(0.0) { 42 | let hachure_gap: F = o.hachure_gap.map(_c).unwrap_or_else(|| _c(-1.0)); 43 | if hachure_gap < _c(0.0) { 44 | o.stroke_width.map(_c::).unwrap_or_else(|| _c(1.0)) * _c(4.0) 45 | } else { 46 | hachure_gap 47 | } 48 | } else { 49 | dash_offset 50 | }; 51 | let dash_gap = o.dash_gap.map(_c).unwrap_or_else(|| _c(-1.0)); 52 | let gap: F = if dash_gap < _c(0.0) { 53 | let hachure_gap = o.hachure_gap.map(_c).unwrap_or_else(|| _c(-1.0)); 54 | if hachure_gap < _c(0.0) { 55 | o.stroke_width.map(_c::).unwrap_or_else(|| _c(1.0)) * _c(4.0) 56 | } else { 57 | hachure_gap 58 | } 59 | } else { 60 | dash_gap 61 | }; 62 | 63 | let mut ops = vec![]; 64 | 65 | for line in lines.iter() { 66 | let length = line.length(); 67 | let count = (length / (offset + gap)).floor(); 68 | let start_offset = (length + gap - (count * (offset + gap))) / _c(2.0); 69 | let mut p1 = line.start_point; 70 | let mut p2 = line.end_point; 71 | if p1.x > p2.x { 72 | p1 = line.end_point; 73 | p2 = line.start_point; 74 | } 75 | let alpha = ((p2.y - p1.y) / (p2.x - p1.x)).atan(); 76 | for i in 0..count.to_u32().unwrap() { 77 | let lstart = F::from(i).unwrap() * (offset + gap); 78 | let lend = lstart + offset; 79 | let start: Point2D = point2( 80 | p1.x + (lstart * num_traits::Float::cos(alpha)) 81 | + (start_offset * num_traits::Float::cos(alpha)), 82 | p1.y + lstart * num_traits::Float::sin(alpha) 83 | + (start_offset * num_traits::Float::sin(alpha)), 84 | ); 85 | let end: Point2D = point2( 86 | p1.x + (lend * num_traits::Float::cos(alpha)) 87 | + (start_offset * num_traits::Float::cos(alpha)), 88 | p1.y + (lend * num_traits::Float::sin(alpha)) 89 | + (start_offset * num_traits::Float::sin(alpha)), 90 | ); 91 | let line_ops = _double_line(start.x, start.y, end.x, end.y, o, false); 92 | ops.extend(line_ops); 93 | } 94 | } 95 | 96 | ops 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /roughr/src/filler/dot_filler.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::BorrowMut; 2 | use std::marker::PhantomData; 3 | 4 | use euclid::default::Point2D; 5 | use euclid::Trig; 6 | use num_traits::{Float, FromPrimitive}; 7 | 8 | use super::scan_line_hachure::polygon_hachure_lines; 9 | use super::traits::PatternFiller; 10 | use crate::core::{OpSet, Options, _c, _cc}; 11 | use crate::geometry::Line; 12 | use crate::renderer::ellipse; 13 | 14 | pub struct DotFiller { 15 | _phantom: PhantomData, 16 | } 17 | 18 | impl PatternFiller for DotFiller 19 | where 20 | F: Float + Trig + FromPrimitive, 21 | P: BorrowMut>>>, 22 | { 23 | fn fill_polygons(&self, mut polygon_list: P, o: &mut Options) -> crate::core::OpSet { 24 | o.set_hachure_angle(Some(0.0)); 25 | let lines = polygon_hachure_lines(polygon_list.borrow_mut(), o); 26 | let ops = DotFiller::dots_on_line(lines, o); 27 | OpSet { 28 | op_set_type: crate::core::OpSetType::FillSketch, 29 | ops, 30 | size: None, 31 | path: None, 32 | } 33 | } 34 | } 35 | impl DotFiller { 36 | pub fn new() -> Self { 37 | DotFiller { _phantom: PhantomData } 38 | } 39 | 40 | fn dots_on_line(lines: Vec>, o: &mut Options) -> Vec> { 41 | let mut ops = vec![]; 42 | let mut gap = o.hachure_gap.map(_c::).unwrap_or_else(|| _c::(-1.0)); 43 | if gap < F::zero() { 44 | gap = o.stroke_width.map(_c::).unwrap_or_else(|| _c::(1.0)) * _c::(4.0); 45 | } 46 | gap = gap.max(_c::(0.1)); 47 | let mut fweight = o.fill_weight.map(_c::).unwrap_or_else(|| _c::(-1.0)); 48 | if fweight < F::zero() { 49 | fweight = o.stroke_width.map(_c::).unwrap_or_else(|| _c::(1.0)) / _c::(2.0); 50 | } 51 | 52 | let ro = gap / _c::(4.0); 53 | for line in lines.iter() { 54 | let length = line.length(); 55 | let dl = length / gap; 56 | let count = dl.ceil() - F::one(); 57 | if count < F::zero() { 58 | continue; 59 | } 60 | let offset = length - (count * gap); 61 | let x = ((line.start_point.x + line.end_point.x) / _c::(2.0)) - (gap / _c::(4.0)); 62 | let min_y = F::min(line.start_point.y, line.end_point.y); 63 | for i in 0..count.to_u64().unwrap() { 64 | let y = min_y + offset + (F::from(i).unwrap() * gap); 65 | let cx = (x - ro) + _cc::(o.random()) * _c::(2.0) * ro; 66 | let cy = (y - ro) + _cc::(o.random()) * _c::(2.0) * ro; 67 | let ellipse_ops = ellipse(cx, cy, fweight, fweight, o); 68 | ops.extend(ellipse_ops.ops); 69 | } 70 | } 71 | 72 | ops 73 | } 74 | } 75 | 76 | impl Default for DotFiller { 77 | fn default() -> Self { 78 | Self::new() 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /roughr/src/filler/hatch_filler.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::BorrowMut; 2 | use std::marker::PhantomData; 3 | 4 | use euclid::default::Point2D; 5 | use euclid::Trig; 6 | use num_traits::{Float, FromPrimitive}; 7 | 8 | use super::scan_line_hachure::ScanlineHachureFiller; 9 | use super::traits::PatternFiller; 10 | use crate::core::Options; 11 | 12 | pub struct HatchFiller { 13 | _phantom: PhantomData, 14 | hachure_filler: ScanlineHachureFiller, 15 | } 16 | 17 | impl PatternFiller for HatchFiller 18 | where 19 | F: Float + Trig + FromPrimitive, 20 | P: BorrowMut>>>, 21 | { 22 | fn fill_polygons(&self, mut polygon_list: P, o: &mut Options) -> crate::core::OpSet { 23 | let mut set1 = self 24 | .hachure_filler 25 | .fill_polygons(polygon_list.borrow_mut(), o); 26 | o.set_hachure_angle(o.hachure_angle.map(|a| a + 90.0)); 27 | let set2 = self.hachure_filler.fill_polygons(polygon_list, o); 28 | set1.ops.extend(set2.ops); 29 | set1 30 | } 31 | } 32 | 33 | impl HatchFiller { 34 | pub fn new() -> Self { 35 | HatchFiller { 36 | _phantom: PhantomData, 37 | hachure_filler: ScanlineHachureFiller::new(), 38 | } 39 | } 40 | } 41 | 42 | impl Default for HatchFiller { 43 | fn default() -> Self { 44 | Self::new() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /roughr/src/filler/mod.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::BorrowMut; 2 | 3 | use euclid::default::Point2D; 4 | use euclid::Trig; 5 | use num_traits::{Float, FromPrimitive}; 6 | 7 | use self::dashed_filler::DashedFiller; 8 | use self::dot_filler::DotFiller; 9 | use self::hatch_filler::HatchFiller; 10 | use self::scan_line_hachure::ScanlineHachureFiller; 11 | use self::traits::PatternFiller; 12 | use self::zig_zag_filler::ZigZagFiller; 13 | use self::zig_zag_line_filler::ZigZagLineFiller; 14 | 15 | pub mod dashed_filler; 16 | pub mod dot_filler; 17 | pub mod hatch_filler; 18 | pub mod scan_line_hachure; 19 | pub mod traits; 20 | pub mod zig_zag_filler; 21 | pub mod zig_zag_line_filler; 22 | 23 | pub enum FillerType { 24 | ScanLineHachure, 25 | DashedFiller, 26 | DotFiller, 27 | HatchFiller, 28 | ZigZagFiller, 29 | ZigZagLineFiller, 30 | } 31 | 32 | pub fn get_filler<'a, F, P>(f: FillerType) -> Box + 'a> 33 | where 34 | F: Float + Trig + FromPrimitive + 'a, 35 | P: BorrowMut>>>, 36 | { 37 | match f { 38 | FillerType::ScanLineHachure => Box::new(ScanlineHachureFiller::new()), 39 | FillerType::DashedFiller => Box::new(DashedFiller::new()), 40 | FillerType::DotFiller => Box::new(DotFiller::new()), 41 | FillerType::HatchFiller => Box::new(HatchFiller::new()), 42 | FillerType::ZigZagFiller => Box::new(ZigZagFiller::new()), 43 | FillerType::ZigZagLineFiller => Box::new(ZigZagLineFiller::new()), 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /roughr/src/filler/scan_line_hachure.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::BorrowMut; 2 | use std::cmp::Ordering; 3 | use std::marker::PhantomData; 4 | 5 | use euclid::default::Point2D; 6 | use euclid::Trig; 7 | use num_traits::{Float, FromPrimitive}; 8 | 9 | use super::traits::PatternFiller; 10 | use crate::core::{OpSet, Options, _c}; 11 | use crate::geometry::{rotate_lines, rotate_points, Line}; 12 | 13 | #[derive(Clone)] 14 | struct EdgeEntry { 15 | pub(crate) ymin: F, 16 | pub(crate) ymax: F, 17 | pub(crate) x: F, 18 | pub(crate) islope: F, 19 | } 20 | 21 | impl std::fmt::Display for EdgeEntry { 22 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 23 | return f.write_str(&format!( 24 | "ymin={} ymax={} x={} islope={}", 25 | self.ymin.to_f64().unwrap(), 26 | self.ymax.to_f64().unwrap(), 27 | self.x.to_f64().unwrap(), 28 | self.islope.to_f64().unwrap() 29 | )); 30 | } 31 | } 32 | 33 | struct ActiveEdgeEntry { 34 | pub(crate) s: F, 35 | pub(crate) edge: EdgeEntry, 36 | } 37 | 38 | pub fn polygon_hachure_lines( 39 | polygon_list: &mut Vec>>, 40 | options: &Options, 41 | ) -> Vec> { 42 | let angle = options.hachure_angle.unwrap_or(0.0) + 90.0; 43 | let mut gap = options.hachure_gap.unwrap_or(0.0); 44 | if gap < 0.0 { 45 | gap = options.stroke_width.unwrap_or(0.0) * 4.0; 46 | } 47 | 48 | gap = f32::max(gap, 0.1); 49 | 50 | let center = Point2D::new(_c(0.0), _c(0.0)); 51 | if angle != 0.0 { 52 | polygon_list 53 | .iter_mut() 54 | .for_each(|polygon| *polygon = rotate_points(polygon, ¢er, _c(angle))) 55 | } 56 | 57 | let mut lines = straight_hachure_lines(polygon_list, _c(gap)); 58 | 59 | if angle != 0.0 { 60 | polygon_list 61 | .iter_mut() 62 | .for_each(|polygon| *polygon = rotate_points(polygon, ¢er, _c(-angle))); 63 | lines = rotate_lines(&lines, ¢er, _c(-angle)); 64 | } 65 | 66 | return lines; 67 | } 68 | 69 | fn straight_hachure_lines(polygon_list: &mut [Vec>], gap: F) -> Vec> 70 | where 71 | F: Float + FromPrimitive + Trig, 72 | { 73 | let mut vertex_array: Vec>> = vec![]; 74 | for polygon in polygon_list.iter_mut() { 75 | if polygon.first() != polygon.last() { 76 | polygon.push( 77 | *polygon 78 | .first() 79 | .expect("can not get first element of polygon"), 80 | ); 81 | } 82 | if polygon.len() > 2 { 83 | vertex_array.push(polygon.clone()); 84 | } 85 | } 86 | 87 | let mut lines: Vec> = vec![]; 88 | let gap = F::max(gap, _c(0.1)); 89 | 90 | // create sorted edges table 91 | let mut edges: Vec> = vec![]; 92 | 93 | for vertices in vertex_array.iter() { 94 | let mut edge_extension = vertices[..] 95 | .windows(2) 96 | .filter_map(|w| { 97 | let p1 = w[0]; 98 | let p2 = w[1]; 99 | if p1.y != p2.y { 100 | let ymin = F::min(p1.y, p2.y); 101 | Some(EdgeEntry { 102 | ymin, 103 | ymax: F::max(p1.y, p2.y), 104 | x: if ymin == p1.y { p1.x } else { p2.x }, 105 | islope: (p2.x - p1.x) / (p2.y - p1.y), 106 | }) 107 | } else { 108 | None 109 | } 110 | }) 111 | .collect::>>(); 112 | 113 | edges.append(&mut edge_extension); 114 | } 115 | 116 | edges.sort_by(|e1, e2| { 117 | if e1.ymin < e2.ymin { 118 | Ordering::Less 119 | } else if e1.ymin > e2.ymin { 120 | Ordering::Greater 121 | } else if e1.x < e2.x { 122 | Ordering::Less 123 | } else if e1.x > e2.x { 124 | Ordering::Greater 125 | } else if e1.ymax == e2.ymax { 126 | Ordering::Equal 127 | } else { 128 | let ordering = (e1.ymax - e2.ymax) / F::abs(e1.ymax - e2.ymax); 129 | if ordering > _c(0.0) { 130 | Ordering::Greater 131 | } else if ordering < _c(0.0) { 132 | Ordering::Less 133 | } else { 134 | Ordering::Equal 135 | } 136 | } 137 | }); 138 | 139 | if edges.is_empty() { 140 | return lines; 141 | } 142 | 143 | let mut active_edges: Vec> = Vec::new(); 144 | let mut y = edges.first().unwrap().ymin; 145 | 146 | loop { 147 | if !edges.is_empty() { 148 | let ix = edges 149 | .iter() 150 | .enumerate() 151 | .find(|(_ind, v)| v.ymin > y) 152 | .map(|(ind, _v)| ind); 153 | 154 | if let Some(indx) = ix { 155 | let removed_elements = edges.splice(0..indx, vec![]); 156 | 157 | removed_elements 158 | .into_iter() 159 | .for_each(|ee| active_edges.push(ActiveEdgeEntry { s: y, edge: ee })); 160 | } else { 161 | let removed_elements = edges.splice(0..edges.len(), vec![]); 162 | 163 | removed_elements 164 | .into_iter() 165 | .for_each(|ee| active_edges.push(ActiveEdgeEntry { s: y, edge: ee })); 166 | } 167 | } 168 | 169 | active_edges.retain(|ae| ae.edge.ymax > y); 170 | 171 | active_edges.sort_by(|ae1, ae2| { 172 | if ae1.edge.x == ae2.edge.x { 173 | Ordering::Equal 174 | } else { 175 | let ratio = (ae1.edge.x - ae2.edge.x) / F::abs(ae1.edge.x - ae2.edge.x); 176 | if ratio > _c(0.0) { 177 | Ordering::Greater 178 | } else { 179 | Ordering::Less 180 | } 181 | } 182 | }); 183 | if active_edges.len() > 1 { 184 | active_edges[..].chunks(2).for_each(|ae| { 185 | let ce = &ae[0]; 186 | let ne = &ae[1]; 187 | lines.push(Line::from(&[ 188 | euclid::Point2D::new(ce.edge.x, y), 189 | euclid::Point2D::new(ne.edge.x, y), 190 | ])); 191 | }); 192 | } 193 | 194 | y = y + gap; 195 | active_edges.iter_mut().for_each(|ae| { 196 | ae.edge.x = ae.edge.x + (gap * ae.edge.islope); 197 | }); 198 | if edges.is_empty() && active_edges.is_empty() { 199 | break; 200 | } 201 | } 202 | 203 | return lines; 204 | } 205 | 206 | pub struct ScanlineHachureFiller { 207 | _phantom: PhantomData, 208 | } 209 | 210 | impl PatternFiller for ScanlineHachureFiller 211 | where 212 | F: Float + Trig + FromPrimitive, 213 | P: BorrowMut>>>, 214 | { 215 | fn fill_polygons(&self, mut polygon_list: P, o: &mut Options) -> crate::core::OpSet { 216 | let lines = polygon_hachure_lines(polygon_list.borrow_mut(), o); 217 | let ops = ScanlineHachureFiller::render_lines(lines, o); 218 | OpSet { 219 | op_set_type: crate::core::OpSetType::FillSketch, 220 | ops: ops, 221 | size: None, 222 | path: None, 223 | } 224 | } 225 | } 226 | 227 | impl ScanlineHachureFiller { 228 | pub fn new() -> Self { 229 | ScanlineHachureFiller { _phantom: PhantomData } 230 | } 231 | 232 | fn render_lines(lines: Vec>, o: &mut Options) -> Vec> { 233 | let mut ops: Vec> = vec![]; 234 | lines.iter().for_each(|l| { 235 | ops.extend(crate::renderer::_double_line( 236 | l.start_point.x, 237 | l.start_point.y, 238 | l.end_point.x, 239 | l.end_point.y, 240 | o, 241 | true, 242 | )) 243 | }); 244 | 245 | ops 246 | } 247 | } 248 | 249 | #[cfg(test)] 250 | mod test { 251 | use euclid::point2; 252 | 253 | use crate::geometry::Line; 254 | 255 | #[test] 256 | fn straight_hachure_lines() { 257 | let mut input = vec![vec![ 258 | point2(0.0, 0.0), 259 | point2(0.0, 1.0), 260 | point2(1.0, 1.0), 261 | point2(1.0, 0.0), 262 | ]]; 263 | let expected = vec![ 264 | Line::from(&[point2(0.0, 0.0), point2(1.0, 0.0)]), 265 | Line::from(&[ 266 | point2(0.0, 0.10000000149011612), 267 | point2(1.0, 0.10000000149011612), 268 | ]), 269 | Line::from(&[ 270 | point2(0.0, 0.20000000298023224), 271 | point2(1.0, 0.20000000298023224), 272 | ]), 273 | Line::from(&[ 274 | point2(0.0, 0.30000000447034836), 275 | point2(1.0, 0.30000000447034836), 276 | ]), 277 | Line::from(&[ 278 | point2(0.0, 0.4000000059604645), 279 | point2(1.0, 0.4000000059604645), 280 | ]), 281 | Line::from(&[ 282 | point2(0.0, 0.5000000074505806), 283 | point2(1.0, 0.5000000074505806), 284 | ]), 285 | Line::from(&[ 286 | point2(0.0, 0.6000000089406967), 287 | point2(1.0, 0.6000000089406967), 288 | ]), 289 | Line::from(&[ 290 | point2(0.0, 0.7000000104308128), 291 | point2(1.0, 0.7000000104308128), 292 | ]), 293 | Line::from(&[ 294 | point2(0.0, 0.800000011920929), 295 | point2(1.0, 0.800000011920929), 296 | ]), 297 | Line::from(&[ 298 | point2(0.0, 0.9000000134110451), 299 | point2(1.0, 0.9000000134110451), 300 | ]), 301 | ]; 302 | let result = super::straight_hachure_lines(&mut input, 0.1); 303 | assert_eq!(expected, result); 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /roughr/src/filler/traits.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::BorrowMut; 2 | 3 | use euclid::default::Point2D; 4 | use euclid::Trig; 5 | use num_traits::{Float, FromPrimitive}; 6 | 7 | use crate::core::{OpSet, Options}; 8 | 9 | pub trait PatternFiller>>>> { 10 | fn fill_polygons(&self, polygon_list: P, o: &mut Options) -> OpSet; 11 | } 12 | -------------------------------------------------------------------------------- /roughr/src/filler/zig_zag_filler.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::BorrowMut; 2 | use std::marker::PhantomData; 3 | 4 | use euclid::default::Point2D; 5 | use euclid::{point2, Trig}; 6 | use num_traits::{Float, FloatConst, FromPrimitive}; 7 | 8 | use super::scan_line_hachure::polygon_hachure_lines; 9 | use super::traits::PatternFiller; 10 | use crate::core::{OpSet, OpSetType, Options, _c}; 11 | use crate::geometry::Line; 12 | 13 | pub struct ZigZagFiller { 14 | _phantom: PhantomData, 15 | } 16 | 17 | impl PatternFiller for ZigZagFiller 18 | where 19 | F: Float + Trig + FromPrimitive, 20 | P: BorrowMut>>>, 21 | { 22 | fn fill_polygons(&self, mut polygon_list: P, o: &mut Options) -> crate::core::OpSet { 23 | let mut gap = o.hachure_gap.map(_c::).unwrap_or_else(|| _c::(-1.0)); 24 | if gap < F::zero() { 25 | gap = o.stroke_width.map(_c::).unwrap_or_else(|| _c::(1.0)) * _c::(4.0); 26 | } 27 | gap = gap.max(_c::(0.1)); 28 | let mut o2 = o.clone(); 29 | o2.set_hachure_gap(Some(gap.to_f32().unwrap())); 30 | let lines = polygon_hachure_lines(polygon_list.borrow_mut(), &o2); 31 | let zig_zag_angle = 32 | (_c::(f32::PI()) / _c::(180.0)) * _c::(o.hachure_angle.unwrap_or(0.0)); 33 | let mut zig_zag_lines = vec![]; 34 | let dgx = gap * _c::(0.5) * Trig::cos(zig_zag_angle); 35 | let dgy = gap * _c::(0.5) * Trig::sin(zig_zag_angle); 36 | 37 | for line in lines.iter() { 38 | if line.length() > _c::(0.0) { 39 | zig_zag_lines.push(Line { 40 | start_point: point2(line.start_point.x - dgx, line.start_point.y + dgy), 41 | end_point: line.end_point, 42 | }); 43 | zig_zag_lines.push(Line { 44 | start_point: point2(line.start_point.x + dgx, line.start_point.y - dgy), 45 | end_point: line.end_point, 46 | }); 47 | } 48 | } 49 | 50 | let ops = ZigZagFiller::render_lines(zig_zag_lines, o); 51 | return OpSet { 52 | ops, 53 | op_set_type: OpSetType::FillSketch, 54 | size: None, 55 | path: None, 56 | }; 57 | } 58 | } 59 | 60 | impl ZigZagFiller { 61 | pub fn new() -> Self { 62 | ZigZagFiller { _phantom: PhantomData } 63 | } 64 | 65 | fn render_lines(lines: Vec>, o: &mut Options) -> Vec> { 66 | let mut ops: Vec> = vec![]; 67 | lines.iter().for_each(|l| { 68 | ops.extend(crate::renderer::_double_line( 69 | l.start_point.x, 70 | l.start_point.y, 71 | l.end_point.x, 72 | l.end_point.y, 73 | o, 74 | true, 75 | )) 76 | }); 77 | 78 | ops 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /roughr/src/filler/zig_zag_line_filler.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::BorrowMut; 2 | use std::marker::PhantomData; 3 | 4 | use euclid::default::Point2D; 5 | use euclid::{point2, Trig}; 6 | use num_traits::{Float, FloatConst, FromPrimitive}; 7 | 8 | use super::scan_line_hachure::polygon_hachure_lines; 9 | use super::traits::PatternFiller; 10 | use crate::core::{Op, OpSet, OpSetType, Options, _c}; 11 | use crate::geometry::Line; 12 | use crate::renderer::_double_line; 13 | 14 | pub struct ZigZagLineFiller { 15 | _phantom: PhantomData, 16 | } 17 | 18 | impl PatternFiller for ZigZagLineFiller 19 | where 20 | F: Float + Trig + FromPrimitive, 21 | P: BorrowMut>>>, 22 | { 23 | fn fill_polygons(&self, mut polygon_list: P, o: &mut Options) -> crate::core::OpSet { 24 | let mut gap = o.hachure_gap.map(_c::).unwrap_or_else(|| _c::(-1.0)); 25 | if gap < F::zero() { 26 | gap = o.stroke_width.map(_c::).unwrap_or_else(|| _c::(1.0)) * _c::(4.0); 27 | } 28 | gap = gap.max(_c::(0.1)); 29 | 30 | let mut zig_zag_offset = o 31 | .zigzag_offset 32 | .map(_c::) 33 | .unwrap_or_else(|| _c::(-1.0)); 34 | if zig_zag_offset < F::zero() { 35 | zig_zag_offset = gap; 36 | } 37 | o.set_hachure_gap(Some((gap + zig_zag_offset).to_f32().unwrap())); 38 | let lines = polygon_hachure_lines(polygon_list.borrow_mut(), o); 39 | OpSet { 40 | op_set_type: OpSetType::FillSketch, 41 | ops: ZigZagLineFiller::zig_zag_lines(&lines, zig_zag_offset, o), 42 | size: None, 43 | path: None, 44 | } 45 | } 46 | } 47 | 48 | impl ZigZagLineFiller { 49 | pub fn new() -> Self { 50 | ZigZagLineFiller { _phantom: PhantomData } 51 | } 52 | 53 | fn zig_zag_lines(lines: &[Line], zig_zag_offset: F, o: &mut Options) -> Vec> { 54 | let mut ops = vec![]; 55 | for line in lines.iter() { 56 | let length = line.length(); 57 | let count = length / (_c::(2.0) * zig_zag_offset); 58 | let mut p1 = line.start_point; 59 | let mut p2 = line.end_point; 60 | if p1.x > p2.x { 61 | p1 = line.end_point; 62 | p2 = line.start_point; 63 | } 64 | 65 | let alpha = ((p2.y - p1.y) / (p2.x - p1.x)).atan(); 66 | 67 | for i in 0..(count.to_isize().unwrap()) { 68 | let lstart = _c::(i as f32) * _c::(2.0) * zig_zag_offset; 69 | let lend = _c::((i + 1) as f32) * _c::(2.0) * zig_zag_offset; 70 | let dz = (zig_zag_offset.powi(2) * _c::(2.0)).sqrt(); 71 | let start: Point2D = point2( 72 | p1.x + lstart * num_traits::Float::cos(alpha), 73 | p1.y + lstart * num_traits::Float::sin(alpha), 74 | ); 75 | let end: Point2D = point2( 76 | p1.x + lend * num_traits::Float::cos(alpha), 77 | p1.y + lend * num_traits::Float::sin(alpha), 78 | ); 79 | let middle: Point2D = point2( 80 | start.x + dz * num_traits::Float::cos(alpha + _c::(f32::PI() / 4.0)), 81 | start.y + dz * num_traits::Float::sin(alpha + _c::(f32::PI() / 4.0)), 82 | ); 83 | ops.extend(_double_line(start.x, start.y, middle.x, middle.y, o, false)); 84 | 85 | ops.extend(_double_line(middle.x, middle.y, end.x, end.y, o, false)); 86 | } 87 | } 88 | ops 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /roughr/src/geometry.rs: -------------------------------------------------------------------------------- 1 | use euclid::default::Point2D; 2 | use euclid::{Angle, Translation2D, Trig, Vector2D}; 3 | use num_traits::{Float, FromPrimitive}; 4 | 5 | use crate::core::_c; 6 | 7 | #[derive(Clone, Debug, PartialEq)] 8 | pub struct Line { 9 | pub start_point: Point2D, 10 | pub end_point: Point2D, 11 | } 12 | 13 | #[derive(Clone, Debug, PartialEq)] 14 | pub struct BezierQuadratic { 15 | pub start: Point2D, 16 | pub cp: Point2D, 17 | pub end: Point2D, 18 | } 19 | 20 | #[derive(Clone, Debug, PartialEq)] 21 | pub struct BezierCubic { 22 | pub start: Point2D, 23 | pub cp1: Point2D, 24 | pub cp2: Point2D, 25 | pub end: Point2D, 26 | } 27 | 28 | impl Line { 29 | pub fn from(points: &[Point2D]) -> Self { 30 | Line { start_point: points[0], end_point: points[1] } 31 | } 32 | pub fn as_points(&self) -> Vec> { 33 | return vec![self.start_point, self.end_point]; 34 | } 35 | 36 | pub fn length(&self) -> F { 37 | (self.end_point - self.start_point).length() 38 | } 39 | 40 | pub fn rotate(&mut self, center: &Point2D, degrees: F) { 41 | let angle = Angle::radians(degrees.to_radians()); 42 | let translation = Translation2D::new(-center.x, -center.y); 43 | let transformation = translation 44 | .to_transform() 45 | .then_rotate(angle) 46 | .then_translate(Vector2D::new(center.x, center.y)); 47 | self.start_point = transformation.transform_point(self.start_point); 48 | self.end_point = transformation.transform_point(self.end_point); 49 | } 50 | } 51 | 52 | pub fn rotate_points( 53 | points: &[Point2D], 54 | center: &Point2D, 55 | degrees: F, 56 | ) -> Vec> { 57 | let angle = Angle::radians(degrees.to_radians()); 58 | let translation = Translation2D::new(-center.x, -center.y); 59 | let transformation = translation 60 | .to_transform() 61 | .then_rotate(angle) 62 | .then_translate(Vector2D::new(center.x, center.y)); 63 | return points 64 | .iter() 65 | .map(|&p| transformation.transform_point(p)) 66 | .collect::>>(); 67 | } 68 | 69 | pub fn rotate_lines( 70 | lines: &[Line], 71 | center: &Point2D, 72 | degrees: F, 73 | ) -> Vec> { 74 | lines 75 | .iter() 76 | .cloned() 77 | .map(|mut l| { 78 | l.rotate(center, degrees); 79 | l 80 | }) 81 | .collect::>>() 82 | } 83 | 84 | /// Raises the order from a quadratic bezier to a cubic bezier curve. 85 | pub fn convert_bezier_quadratic_to_cubic( 86 | bezier_quadratic: BezierQuadratic, 87 | ) -> BezierCubic { 88 | let cubic_x1 = bezier_quadratic.start.x 89 | + _c::(2.0 / 3.0) * (bezier_quadratic.cp.x - bezier_quadratic.start.x); 90 | let cubic_y1 = bezier_quadratic.start.y 91 | + _c::(2.0 / 3.0) * (bezier_quadratic.cp.y - bezier_quadratic.start.y); 92 | let cubic_x2 = bezier_quadratic.end.x 93 | + _c::(2.0 / 3.0) * (bezier_quadratic.cp.x - bezier_quadratic.end.x); 94 | let cubic_y2 = bezier_quadratic.end.y 95 | + _c::(2.0 / 3.0) * (bezier_quadratic.cp.y - bezier_quadratic.end.y); 96 | 97 | BezierCubic { 98 | start: bezier_quadratic.start, 99 | cp1: Point2D::new(cubic_x1, cubic_y1), 100 | cp2: Point2D::new(cubic_x2, cubic_y2), 101 | end: bezier_quadratic.end, 102 | } 103 | } 104 | 105 | #[cfg(test)] 106 | mod tests { 107 | use euclid::default::Point2D; 108 | #[test] 109 | fn line_length() { 110 | let l = super::Line::from(&[Point2D::new(1.0, 1.0), Point2D::new(2.0, 2.0)]); 111 | assert_eq!(l.length(), f32::sqrt(2.0)); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /roughr/src/lib.rs: -------------------------------------------------------------------------------- 1 | // This crate is entirely safe 2 | #![forbid(unsafe_code)] 3 | // Ensures that `pub` means published in the public API. 4 | // This property is useful for reasoning about breaking API changes. 5 | #![deny(unreachable_pub)] 6 | 7 | //! 8 | //! This crate is a rustlang port of [Rough.js](https://github.com/rough-stuff/rough) npm package written by 9 | //! [@pshihn](https://github.com/pshihn). 10 | //! 11 | //! This package exposes functions to generate rough drawing primitives which looks like hand drawn sketches. 12 | //! This is the core create of operations to create rough drawings. It exposes its own primitive drawing types for lines 13 | //! curves, arcs, polygons, circles, ellipses and even svg paths. 14 | //! Works on [Point2D](https://docs.rs/euclid/0.22.7/euclid/struct.Point2D.html) type from [euclid](https://github.com/servo/euclid) crate 15 | //! 16 | //! On its own this crate can not draw on any context. One needs to use existing drawing libraries such as [piet](https://github.com/linebender/piet), 17 | //! [raqote](https://github.com/jrmuizel/raqote), [tiny-skia](https://github.com/RazrFalcon/tiny-skia) etc in combination with 18 | //! roughr. In this workspace an example adapter is implemented for [piet](https://github.com/linebender/piet). Below examples are 19 | //! output of [rough_piet](https://github.com/orhanbalci/rough-rs/tree/main/rough_piet) adapter. 20 | //! 21 | //! ## 📦 Cargo.toml 22 | //! 23 | //! ```toml 24 | //! [dependencies] 25 | //! roughr = "0.1" 26 | //! ``` 27 | //! 28 | //! ## 🔧 Example 29 | //! 30 | //! ### Rectangle 31 | //! 32 | //! ```ignore 33 | //! let options = OptionsBuilder::default() 34 | //! .stroke(Srgba::from_raw(&[114u8, 87u8, 82u8, 255u8]).into_format()) 35 | //! .fill(Srgba::from_raw(&[254u8, 246u8, 201u8, 255u8]).into_format()) 36 | //! .fill_style(FillStyle::Hachure) 37 | //! .fill_weight(DPI * 0.01) 38 | //! .build() 39 | //! .unwrap(); 40 | //! let generator = KurboGenerator::new(options); 41 | //! let rect_width = 100.0; 42 | //! let rect_height = 50.0; 43 | //! let rect = generator.rectangle::( 44 | //! (WIDTH as f32 - rect_width) / 2.0, 45 | //! (HEIGHT as f32 - rect_height) / 2.0, 46 | //! rect_width, 47 | //! rect_height, 48 | //! ); 49 | //! let background_color = Color::from_hex_str("96C0B7").unwrap(); 50 | //! 51 | //! rc.fill( 52 | //! Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64), 53 | //! &background_color, 54 | //! ); 55 | //! rect.draw(&mut rc); 56 | //! ``` 57 | //! 58 | //! ### 🖨️ Output Rectangle 59 | //! ![rectangle](https://raw.githubusercontent.com/orhanbalci/rough-rs/main/roughr/assets/rectangle.png) 60 | //! 61 | //! ### Circle 62 | //! 63 | //! ```ignore 64 | //! let options = OptionsBuilder::default() 65 | //! .stroke(Srgba::from_raw(&[114u8, 87u8, 82u8, 255u8]).into_format()) 66 | //! .fill(Srgba::from_raw(&[254u8, 246u8, 201u8, 255u8]).into_format()) 67 | //! .fill_style(FillStyle::Hachure) 68 | //! .fill_weight(DPI * 0.01) 69 | //! .build() 70 | //! .unwrap(); 71 | //! let generator = KurboGenerator::new(options); 72 | //! let circle_paths = generator.circle::( 73 | //! (WIDTH as f32) / 2.0, 74 | //! (HEIGHT as f32) / 2.0, 75 | //! HEIGHT as f32 - 10.0f32, 76 | //! ); 77 | //! let background_color = Color::from_hex_str("96C0B7").unwrap(); 78 | //! 79 | //! rc.fill( 80 | //! Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64), 81 | //! &background_color, 82 | //! ); 83 | //! circle_paths.draw(&mut rc); 84 | //! ``` 85 | //! 86 | //! ### 🖨️ Output Circle 87 | //! ![circle](https://raw.githubusercontent.com/orhanbalci/rough-rs/main/roughr/assets/circle.png) 88 | //! 89 | //! 90 | //! ### Ellipse 91 | //! 92 | //! ```ignore 93 | //! let options = OptionsBuilder::default() 94 | //! .stroke(Srgba::from_raw(&[114u8, 87u8, 82u8, 255u8]).into_format()) 95 | //! .fill(Srgba::from_raw(&[254u8, 246u8, 201u8, 255u8]).into_format()) 96 | //! .fill_style(FillStyle::Hachure) 97 | //! .fill_weight(DPI * 0.01) 98 | //! .build() 99 | //! .unwrap(); 100 | //! let generator = KurboGenerator::new(options); 101 | //! let ellipse_paths = generator.ellipse::( 102 | //! (WIDTH as f32) / 2.0, 103 | //! (HEIGHT as f32) / 2.0, 104 | //! WIDTH as f32 - 10.0, 105 | //! HEIGHT as f32 - 10.0, 106 | //! ); 107 | //! let background_color = Color::from_hex_str("96C0B7").unwrap(); 108 | //! 109 | //! rc.fill( 110 | //! Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64), 111 | //! &background_color, 112 | //! ); 113 | //! ellipse_paths.draw(&mut rc); 114 | //! ``` 115 | //! 116 | //! ### 🖨️ Output Ellipse 117 | //! ![ellipse](https://raw.githubusercontent.com/orhanbalci/rough-rs/main/roughr/assets/ellipse.png) 118 | //! 119 | //! 120 | //! ### Svg Path 121 | //! 122 | //! ```ignore 123 | //! let options = OptionsBuilder::default() 124 | //! .stroke(Srgba::from_raw(&[114u8, 87u8, 82u8, 255u8]).into_format()) 125 | //! .fill(Srgba::from_raw(&[254u8, 246u8, 201u8, 255u8]).into_format()) 126 | //! .fill_style(FillStyle::Hachure) 127 | //! .fill_weight(DPI * 0.01) 128 | //! .build() 129 | //! .unwrap(); 130 | //! let generator = KurboGenerator::new(options); 131 | //! let heart_svg_path = "M140 20C73 20 20 74 20 140c0 135 136 170 228 303 88-132 229-173 229-303 0-66-54-120-120-120-48 0-90 28-109 69-19-41-60-69-108-69z".into(); 132 | //! let heart_svg_path_drawing = generator.path::(heart_svg_path); 133 | //! let background_color = Color::from_hex_str("96C0B7").unwrap(); 134 | //! 135 | //! rc.fill( 136 | //! Rect::new(0.0, 0.0, WIDTH as f64, HEIGHT as f64), 137 | //! &background_color, 138 | //! ); 139 | //! heart_svg_path_drawing.draw(&mut rc); 140 | //! ``` 141 | //! 142 | //! ### 🖨️ Output Svg Path 143 | //! ![svgheart](https://raw.githubusercontent.com/orhanbalci/rough-rs/main/roughr/assets/heart_svg_path.png) 144 | //! 145 | //! ## Filler Implementation Status 146 | //! - [x] Hachure 147 | //! - [x] Zigzag 148 | //! - [x] Cross-Hatch 149 | //! - [x] Dots 150 | //! - [x] Dashed 151 | //! - [x] Zigzag-Line 152 | //! 153 | //! ## 🔭 Examples 154 | //! 155 | //! For more examples have a look at the 156 | //! [examples](https://github.com/orhanbalci/rough-rs/tree/main/rough_piet/examples) folder. 157 | 158 | #[macro_use] 159 | extern crate derive_builder; 160 | 161 | pub mod core; 162 | pub mod filler; 163 | pub mod generator; 164 | pub mod geometry; 165 | pub mod points_on_path; 166 | pub mod renderer; 167 | 168 | pub use euclid::Point2D; 169 | pub use palette::Srgba; 170 | pub use svgtypes::*; 171 | -------------------------------------------------------------------------------- /roughr/src/points_on_path.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | use std::ops::MulAssign; 3 | 4 | use euclid::default::Point2D; 5 | use euclid::Trig; 6 | use num_traits::{Float, FromPrimitive}; 7 | use points_on_curve::{points_on_bezier_curves, simplify}; 8 | use svg_path_ops::{absolutize, normalize}; 9 | use svgtypes::{PathParser, PathSegment}; 10 | 11 | use crate::core::{_c, _cc}; 12 | 13 | pub fn points_on_path( 14 | path: String, 15 | tolerance: Option, 16 | distance: Option, 17 | ) -> Vec>> 18 | where 19 | F: FromPrimitive + Trig + Float + MulAssign + Display, 20 | { 21 | let path_parser = PathParser::from(path.as_ref()); 22 | let path_segments: Vec = path_parser.flatten().collect(); 23 | let normalized_segments = normalize(absolutize(path_segments.iter())); 24 | 25 | generate_points(tolerance, distance, normalized_segments) 26 | } 27 | 28 | pub fn points_on_segments( 29 | path_segments: Vec, 30 | tolerance: Option, 31 | distance: Option, 32 | ) -> Vec>> 33 | where 34 | F: FromPrimitive + Trig + Float + MulAssign + Display, 35 | { 36 | let normalized_segments = normalize(absolutize(path_segments.iter())); 37 | generate_points(tolerance, distance, normalized_segments) 38 | } 39 | 40 | fn generate_points( 41 | tolerance: Option, 42 | distance: Option, 43 | normalized_segments: impl Iterator, 44 | ) -> Vec>> 45 | where 46 | F: FromPrimitive + Trig + Float + MulAssign + Display, 47 | { 48 | let mut sets: Vec>> = vec![]; 49 | let mut current_points: Vec> = vec![]; 50 | let mut start = Point2D::new(_c::(0.0), _c::(0.0)); 51 | let mut pending_curve: Vec> = vec![]; 52 | 53 | let append_pending_curve = 54 | |current_points: &mut Vec>, pending_curve: &mut Vec>| { 55 | if pending_curve.len() >= 4 { 56 | current_points.append(&mut points_on_bezier_curves( 57 | &pending_curve[..], 58 | tolerance.unwrap_or(_c(0.0)), 59 | None, 60 | )); 61 | } 62 | pending_curve.clear(); 63 | }; 64 | 65 | let mut append_pending_points = 66 | |current_points: &mut Vec>, pending_curve: &mut Vec>| { 67 | { 68 | append_pending_curve(current_points, pending_curve); 69 | } 70 | if !current_points.is_empty() { 71 | sets.push(current_points.clone()); 72 | current_points.clear(); 73 | } 74 | }; 75 | 76 | for segment in normalized_segments { 77 | match segment { 78 | PathSegment::MoveTo { abs: true, x, y } => { 79 | append_pending_points(&mut current_points, &mut pending_curve); 80 | start = Point2D::new(_cc::(x), _cc::(y)); 81 | current_points.push(start); 82 | } 83 | PathSegment::LineTo { abs: true, x, y } => { 84 | append_pending_curve(&mut current_points, &mut pending_curve); 85 | current_points.push(Point2D::new(_cc::(x), _cc::(y))); 86 | } 87 | PathSegment::CurveTo { abs: true, x1, y1, x2, y2, x, y } => { 88 | if pending_curve.is_empty() { 89 | let last_point = if !current_points.is_empty() { 90 | current_points.last().unwrap() 91 | } else { 92 | &start 93 | }; 94 | pending_curve.push(*last_point); 95 | } 96 | pending_curve.push(Point2D::new(_cc::(x1), _cc::(y1))); 97 | pending_curve.push(Point2D::new(_cc::(x2), _cc::(y2))); 98 | pending_curve.push(Point2D::new(_cc::(x), _cc::(y))); 99 | } 100 | PathSegment::ClosePath { abs: true } => { 101 | append_pending_curve(&mut current_points, &mut pending_curve); 102 | current_points.push(start); 103 | } 104 | _ => panic!("unexpected path segment"), 105 | } 106 | } 107 | 108 | append_pending_points(&mut current_points, &mut pending_curve); 109 | 110 | if let Some(dst) = distance { 111 | let mut out = vec![]; 112 | for set in sets.iter() { 113 | let simplified_set = simplify(set, dst); 114 | if !simplified_set.is_empty() { 115 | out.push(simplified_set); 116 | } 117 | } 118 | out 119 | } else { 120 | sets 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | imports_granularity = "Module" 2 | group_imports = "StdExternalCrate" 3 | imports_layout = "HorizontalVertical" 4 | struct_lit_width = 50 5 | struct_variant_width = 50 6 | format_code_in_doc_comments = true -------------------------------------------------------------------------------- /svg_path_ops/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "svg_path_ops" 3 | version = "0.11.0" 4 | edition = "2021" 5 | authors = ["orhanbalci@gmail.com "] 6 | description = "SVG Path Manipulation Utilities" 7 | repository = "https://github.com/orhanbalci/rough-rs.git" 8 | homepage = "https://github.com/orhanbalci" 9 | keywords = ["graphics", "bezier", "sketch", "2D", "svg"] 10 | categories = ["graphics"] 11 | license = "MIT" 12 | readme = "README.md" 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | 16 | [dependencies] 17 | svgtypes = "0.11" 18 | cgmath = "0.18" 19 | -------------------------------------------------------------------------------- /svg_path_ops/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Orhan Balci 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /svg_path_ops/README.md: -------------------------------------------------------------------------------- 1 | # svg_path_ops 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/svg_path_ops.svg)](https://crates.io/crates/svg_path_ops) 4 | [![Documentation](https://docs.rs/svg_path_ops/badge.svg)](https://docs.rs/svg_path_ops) 5 | [![License](https://img.shields.io/github/license/orhanbalci/rough-rs.svg)](https://github.com/orhanbalci/rough-rs/blob/main/svg_path_ops/LICENSE) 6 | 7 | 8 | 9 | 10 | This crate includes utility functions to work with svg paths. Works on types from [svgtypes](https://github.com/RazrFalcon/svgtypes) 11 | crate. 12 | 13 | This package exposes functions to manipulate svg paths with simplification purposes. Also a path transformer fully compatible with 14 | [svgpath](https://github.com/fontello/svgpath) is provided. 15 | 16 | 17 | ## 📦 Cargo.toml 18 | 19 | ```toml 20 | [dependencies] 21 | svg_path_ops = "0.6" 22 | ``` 23 | 24 | ## 🔧 Example 25 | 26 | ### Translate 27 | 28 | ``` rust,ignore 29 | let translated_path = PathTransformer::new(cat_svg_path) 30 | .translate(230.0, 0.0) 31 | .to_string(); 32 | ``` 33 | 34 | [full example](https://github.com/orhanbalci/rough-rs/blob/main/rough_piet/examples/translate.rs) 35 | 36 | ### 🖨️ Output Translate 37 | ![translate](https://raw.githubusercontent.com/orhanbalci/rough-rs/main/svg_path_ops/assets/translated_cat.png) 38 | 39 | ### Rotate 40 | 41 | ``` rust,ignore 42 | let translated_path = PathTransformer::new(cat_svg_path) 43 | .rotate(90.0, 126.0, 140.0) 44 | .translate(220.0, 0.0) 45 | .to_string(); 46 | ``` 47 | 48 | [full example](https://github.com/orhanbalci/rough-rs/blob/main/rough_piet/examples/rotate.rs) 49 | 50 | ### 🖨️ Output Rotate 51 | ![translate](https://raw.githubusercontent.com/orhanbalci/rough-rs/main/svg_path_ops/assets/rotated_cat.png) 52 | 53 | ### Skew 54 | ``` rust,ignore 55 | let translated_path = PathTransformer::new(cat_svg_path) 56 | .skew_x(20.0) 57 | .translate(180.0, 0.0) 58 | .to_string(); 59 | ``` 60 | 61 | [full example](https://github.com/orhanbalci/rough-rs/blob/main/rough_piet/examples/skew.rs) 62 | 63 | ### 🖨️ Output Skew 64 | ![translate](https://raw.githubusercontent.com/orhanbalci/rough-rs/main/svg_path_ops/assets/skewed_cat.png) 65 | 66 | ### Scale 67 | ``` rust,ignore 68 | let translated_path = PathTransformer::new(cat_svg_path) 69 | .scale(0.5, 0.5) 70 | .translate(220.0, 60.0) 71 | .to_string(); 72 | ``` 73 | 74 | [full example](https://github.com/orhanbalci/rough-rs/blob/main/rough_piet/examples/scale.rs) 75 | 76 | ### 🖨️ Output Scale 77 | ![translate](https://raw.githubusercontent.com/orhanbalci/rough-rs/main/svg_path_ops/assets/scaled_cat.png) 78 | 79 | 80 | 81 | ## 📝 License 82 | 83 | Licensed under MIT License ([LICENSE](LICENSE)). 84 | 85 | ### 🚧 Contributions 86 | 87 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the MIT license, shall be licensed as above, without any additional terms or conditions. 88 | -------------------------------------------------------------------------------- /svg_path_ops/assets/rotated_cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhanbalci/rough-rs/9449e874ef680fdf9afd6d04ca6fe69c0df9862d/svg_path_ops/assets/rotated_cat.png -------------------------------------------------------------------------------- /svg_path_ops/assets/scaled_cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhanbalci/rough-rs/9449e874ef680fdf9afd6d04ca6fe69c0df9862d/svg_path_ops/assets/scaled_cat.png -------------------------------------------------------------------------------- /svg_path_ops/assets/skewed_cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhanbalci/rough-rs/9449e874ef680fdf9afd6d04ca6fe69c0df9862d/svg_path_ops/assets/skewed_cat.png -------------------------------------------------------------------------------- /svg_path_ops/assets/translated_cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orhanbalci/rough-rs/9449e874ef680fdf9afd6d04ca6fe69c0df9862d/svg_path_ops/assets/translated_cat.png -------------------------------------------------------------------------------- /svg_path_ops/src/a2c.rs: -------------------------------------------------------------------------------- 1 | use core::f64; 2 | 3 | const TAU: f64 = f64::consts::PI * 2.0; 4 | 5 | // Calculate an angle between two unit vectors 6 | // 7 | // Since we measure angle between radii of circular arcs, 8 | // we can use simplified math (without length normalization) 9 | // 10 | fn unit_vector_angle(ux: f64, uy: f64, vx: f64, vy: f64) -> f64 { 11 | let sign = if (ux * vy) - (uy * vx) < 0.0 { 12 | -1.0 13 | } else { 14 | 1.0 15 | }; 16 | let mut dot = ux * vx + uy * vy; 17 | 18 | // Add this to work with arbitrary vectors: 19 | // dot /= Math.sqrt(ux * ux + uy * uy) * Math.sqrt(vx * vx + vy * vy); 20 | 21 | // rounding errors, e.g. -1.0000000000000002 can screw up this 22 | if dot > 1.0 { 23 | dot = 1.0; 24 | } 25 | if dot < -1.0 { 26 | dot = -1.0; 27 | } 28 | 29 | sign * dot.acos() 30 | } 31 | 32 | // Convert from endpoint to center parameterization, 33 | // see http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes 34 | // 35 | // Return [cx, cy, theta1, delta_theta] 36 | // 37 | fn get_arc_center( 38 | x1: f64, 39 | y1: f64, 40 | x2: f64, 41 | y2: f64, 42 | fa: bool, 43 | fs: bool, 44 | rx: f64, 45 | ry: f64, 46 | sin_phi: f64, 47 | cos_phi: f64, 48 | ) -> [f64; 4] { 49 | // Step 1. 50 | // 51 | // Moving an ellipse so origin will be the middlepoint between our two 52 | // points. After that, rotate it to line up ellipse axes with coordinate 53 | // axes. 54 | // 55 | let x1p = cos_phi * (x1 - x2) / 2.0 + sin_phi * (y1 - y2) / 2.0; 56 | let y1p = -sin_phi * (x1 - x2) / 2.0 + cos_phi * (y1 - y2) / 2.0; 57 | 58 | let rx_sq = rx * rx; 59 | let ry_sq = ry * ry; 60 | let x1p_sq = x1p * x1p; 61 | let y1p_sq = y1p * y1p; 62 | 63 | // Step 2. 64 | // 65 | // Compute coordinates of the centre of this ellipse (cx', cy') 66 | // in the new coordinate system. 67 | // 68 | let mut radicant = (rx_sq * ry_sq) - (rx_sq * y1p_sq) - (ry_sq * x1p_sq); 69 | 70 | if radicant < 0.0 { 71 | // due to rounding errors it might be e.g. -1.3877787807814457e-17 72 | radicant = 0.0; 73 | } 74 | 75 | radicant /= (rx_sq * y1p_sq) + (ry_sq * x1p_sq); 76 | radicant = radicant.sqrt() * if fa == fs { -1.0 } else { 1.0 }; 77 | 78 | let cxp = radicant * rx / ry * y1p; 79 | let cyp = radicant * -ry / rx * x1p; 80 | 81 | // Step 3. 82 | // 83 | // Transform back to get centre coordinates (cx, cy) in the original 84 | // coordinate system. 85 | // 86 | let cx = cos_phi * cxp - sin_phi * cyp + (x1 + x2) / 2.0; 87 | let cy = sin_phi * cxp + cos_phi * cyp + (y1 + y2) / 2.0; 88 | 89 | // Step 4. 90 | // 91 | // Compute angles (theta1, delta_theta). 92 | // 93 | let v1x = (x1p - cxp) / rx; 94 | let v1y = (y1p - cyp) / ry; 95 | let v2x = (-x1p - cxp) / rx; 96 | let v2y = (-y1p - cyp) / ry; 97 | 98 | let theta1 = unit_vector_angle(1.0, 0.0, v1x, v1y); 99 | let mut delta_theta = unit_vector_angle(v1x, v1y, v2x, v2y); 100 | 101 | if !fs && delta_theta > 0.0 { 102 | delta_theta -= TAU; 103 | } 104 | if fs && delta_theta < 0.0 { 105 | delta_theta += TAU; 106 | } 107 | 108 | [cx, cy, theta1, delta_theta] 109 | } 110 | 111 | // 112 | // Approximate one unit arc segment with bézier curves, 113 | // see http://math.stackexchange.com/questions/873224 114 | // 115 | fn approximate_unit_arc(theta1: f64, delta_theta: f64) -> [f64; 8] { 116 | let alpha = 4.0 / 3.0 * (delta_theta / 4.0).tan(); 117 | 118 | let x1 = theta1.cos(); 119 | let y1 = theta1.sin(); 120 | let x2 = (theta1 + delta_theta).cos(); 121 | let y2 = (theta1 + delta_theta).sin(); 122 | 123 | return [ 124 | x1, 125 | y1, 126 | x1 - y1 * alpha, 127 | y1 + x1 * alpha, 128 | x2 + y2 * alpha, 129 | y2 - x2 * alpha, 130 | x2, 131 | y2, 132 | ]; 133 | } 134 | 135 | pub fn a2c( 136 | x1: f64, 137 | y1: f64, 138 | x2: f64, 139 | y2: f64, 140 | fa: bool, 141 | fs: bool, 142 | rx: f64, 143 | ry: f64, 144 | phi: f64, 145 | ) -> Vec<[f64; 8]> { 146 | let sin_phi = (phi * TAU / 360.0).sin(); 147 | let cos_phi = (phi * TAU / 360.0).cos(); 148 | 149 | // Make sure radii are valid 150 | // 151 | let x1p = cos_phi * (x1 - x2) / 2.0 + sin_phi * (y1 - y2) / 2.0; 152 | let y1p = -sin_phi * (x1 - x2) / 2.0 + cos_phi * (y1 - y2) / 2.0; 153 | 154 | if x1p == 0.0 && y1p == 0.0 { 155 | // we're asked to draw line to itself 156 | return vec![]; 157 | } 158 | 159 | if rx == 0.0 || ry == 0.0 { 160 | // one of the radii is zero 161 | return vec![]; 162 | } 163 | 164 | // Compensate out-of-range radii 165 | // 166 | let mut rx = rx.abs(); 167 | let mut ry = ry.abs(); 168 | 169 | let lambda = (x1p * x1p) / (rx * rx) + (y1p * y1p) / (ry * ry); 170 | if lambda > 1.0 { 171 | rx *= lambda.sqrt(); 172 | ry *= lambda.sqrt(); 173 | } 174 | 175 | // Get center parameters (cx, cy, theta1, delta_theta) 176 | // 177 | let cc = get_arc_center(x1, y1, x2, y2, fa, fs, rx, ry, sin_phi, cos_phi); 178 | 179 | let mut result = vec![]; 180 | let mut theta1 = cc[2]; 181 | let mut delta_theta = cc[3]; 182 | 183 | // Split an arc to multiple segments, so each segment 184 | // will be less than τ/4 (= 90°) 185 | // 186 | let segments = *[(delta_theta.abs() / (TAU / 4.0)).ceil(), 1.0] 187 | .iter() 188 | .max_by(|a, b| a.total_cmp(b)) 189 | .expect("can not find max") as u32; 190 | delta_theta /= segments as f64; 191 | 192 | for _ in 0..segments { 193 | result.push(approximate_unit_arc(theta1, delta_theta)); 194 | theta1 += delta_theta; 195 | } 196 | 197 | // We have a bezier approximation of a unit circle, 198 | // now need to transform back to the original ellipse 199 | // 200 | return result 201 | .iter_mut() 202 | .map(|curve| { 203 | for i in (0..curve.len()).step_by(2) { 204 | let mut x = curve[i + 0]; 205 | let mut y = curve[i + 1]; 206 | 207 | // scale 208 | x *= rx; 209 | y *= ry; 210 | 211 | // rotate 212 | let xp = cos_phi * x - sin_phi * y; 213 | let yp = sin_phi * x + cos_phi * y; 214 | 215 | // translate 216 | curve[i + 0] = xp + cc[0]; 217 | curve[i + 1] = yp + cc[1]; 218 | } 219 | 220 | return *curve; 221 | }) 222 | .collect(); 223 | } 224 | -------------------------------------------------------------------------------- /svg_path_ops/src/ellipse.rs: -------------------------------------------------------------------------------- 1 | use std::f64::consts::PI; 2 | 3 | use cgmath::{Angle, Deg, Rad}; 4 | 5 | pub struct Ellipse { 6 | pub rx: f64, 7 | pub ry: f64, 8 | pub ax: Deg, 9 | } 10 | 11 | const EPSILON: f64 = 1e-10; 12 | 13 | impl Ellipse { 14 | // constructor 15 | // an ellipse centred at 0 with radii rx,ry and x - axis - angle ax. 16 | pub fn new(rx: f64, ry: f64, ax: f64) -> Self { 17 | Ellipse { rx, ry, ax: Deg(ax) } 18 | } 19 | 20 | pub fn transform(&mut self, matrix: &[f64; 4]) -> &mut Self { 21 | let c = Rad::from(self.ax).cos(); 22 | let s = Rad::from(self.ax).sin(); 23 | let ma = [ 24 | self.rx * (matrix[0] * c + matrix[2] * s), 25 | self.rx * (matrix[1] * c + matrix[3] * s), 26 | self.ry * (-matrix[0] * s + matrix[2] * c), 27 | self.ry * (-matrix[1] * s + matrix[3] * c), 28 | ]; 29 | 30 | let j = ma[0] * ma[0] + ma[2] * ma[2]; 31 | let k = ma[1] * ma[1] + ma[3] * ma[3]; 32 | 33 | // the discriminant of the characteristic polynomial of ma * transpose(ma) 34 | let d = ((ma[0] - ma[3]) * (ma[0] - ma[3]) + (ma[2] + ma[1]) * (ma[2] + ma[1])) 35 | * ((ma[0] + ma[3]) * (ma[0] + ma[3]) + (ma[2] - ma[1]) * (ma[2] - ma[1])); 36 | 37 | // the "mean eigenvalue" 38 | let jk = (j + k) / 2.0; 39 | 40 | // check if the image is (almost) a circle 41 | if d < EPSILON * jk { 42 | // if it is 43 | self.rx = jk.sqrt(); 44 | self.ry = jk.sqrt(); 45 | self.ax = Deg(0.0); 46 | self 47 | } else { 48 | // if it is not a circle 49 | let l = ma[0] * ma[1] + ma[2] * ma[3]; 50 | 51 | let d = d.sqrt(); 52 | 53 | // {l1,l2} = the two eigen values of ma * transpose(ma) 54 | let l1 = jk + d / 2.0; 55 | let l2 = jk - d / 2.0; 56 | // the x - axis - rotation angle is the argument of the l1 - eigenvector 57 | self.ax = Deg(if l.abs() < EPSILON && (l1 - k).abs() < EPSILON { 58 | 90.0 59 | } else { 60 | if l.abs() > (l1 - k).abs() { 61 | (l1 - j) / l 62 | } else { 63 | l / (l1 - k) 64 | } 65 | .atan() 66 | * 180.0 67 | / PI 68 | }); 69 | 70 | // if ax > 0 => rx = sqrt(l1), ry = sqrt(l2), else exchange axes and ax += 90 71 | if self.ax >= Deg(0.0) { 72 | // if ax in [0,90] 73 | self.rx = l1.sqrt(); 74 | self.ry = l2.sqrt(); 75 | } else { 76 | // if ax in ]-90,0[ => exchange axes 77 | self.ax += Deg(90.0); 78 | self.rx = l2.sqrt(); 79 | self.ry = l1.sqrt(); 80 | } 81 | self 82 | } 83 | } 84 | 85 | pub fn is_degenrate(&self) -> bool { 86 | self.rx < EPSILON * self.ry || self.ry < EPSILON * self.rx 87 | } 88 | } 89 | 90 | #[cfg(test)] 91 | mod test {} 92 | --------------------------------------------------------------------------------