├── example.png ├── .gitignore ├── release.toml ├── README.tpl ├── examples ├── stroke.rs ├── stroke-arc.rs ├── sweep-gradient.rs ├── pad.rs ├── text.rs └── capabilities.rs ├── benches └── blend.rs ├── Cargo.toml ├── .github └── workflows │ └── rust.yml ├── DESIGN.md ├── LICENSE.md ├── src ├── geom.rs ├── lib.rs ├── dash.rs ├── path_builder.rs ├── stroke.rs ├── rasterizer.rs ├── blitter.rs ├── tests.rs └── draw_target.rs └── README.md /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrmuizel/raqote/HEAD/example.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | .idea 5 | .IDEAS.swp 6 | -------------------------------------------------------------------------------- /release.toml: -------------------------------------------------------------------------------- 1 | pre-release-commit-message = "Release {{version}}" 2 | post-release-commit-message = "Bump version" 3 | -------------------------------------------------------------------------------- /README.tpl: -------------------------------------------------------------------------------- 1 | ## {{crate}} 2 | [![Build Status](https://github.com/jrmuizel/{{crate}}/actions/workflows/rust.yml/badge.svg)](https://github.com/jrmuizel/{{crate}}/actions) 3 | [![crates.io](https://img.shields.io/crates/v/{{crate}}.svg)](https://crates.io/crates/{{crate}}) 4 | [![Documentation](https://docs.rs/{{crate}}/badge.svg)](https://docs.rs/{{crate}}) 5 | 6 | {{readme}} 7 | -------------------------------------------------------------------------------- /examples/stroke.rs: -------------------------------------------------------------------------------- 1 | use raqote::*; 2 | 3 | fn main() { 4 | let mut dt = DrawTarget::new(400, 400); 5 | 6 | let mut pb = PathBuilder::new(); 7 | pb.move_to(200., 200.); 8 | pb.line_to(300., 300.); 9 | pb.line_to(200., 300.); 10 | 11 | let path = pb.finish(); 12 | dt.stroke( 13 | &path, 14 | &Source::Solid(SolidSource::from_unpremultiplied_argb(0xFF, 0, 0x80, 0)), 15 | &StrokeStyle { 16 | width: 100000., // <-- 17 | ..StrokeStyle::default() 18 | }, 19 | &DrawOptions::new(), 20 | ); 21 | 22 | dt.write_png("out.png").unwrap(); 23 | } -------------------------------------------------------------------------------- /examples/stroke-arc.rs: -------------------------------------------------------------------------------- 1 | use lyon_geom::Transform; 2 | use raqote::*; 3 | 4 | fn main() { 5 | let mut dt = DrawTarget::new(400, 400); 6 | 7 | let mut pb = PathBuilder::new(); 8 | pb.arc(0., 0., 20., 0., std::f32::consts::PI); 9 | 10 | let path = pb.finish(); 11 | dt.set_transform(&Transform::translation(50., 50.)); 12 | dt.stroke( 13 | &path, 14 | &Source::Solid(SolidSource::from_unpremultiplied_argb(0xFF, 0, 0x80, 0)), 15 | &StrokeStyle { 16 | width: 40., // <-- 17 | ..StrokeStyle::default() 18 | }, 19 | &DrawOptions::new(), 20 | ); 21 | 22 | dt.write_png("out.png").unwrap(); 23 | } -------------------------------------------------------------------------------- /benches/blend.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | extern crate test; 3 | use test::bench::Bencher; 4 | #[bench] 5 | fn bench_raqote(b: &mut Bencher) { 6 | use raqote::*; 7 | 8 | let mut dt = DrawTarget::new(250, 250); 9 | 10 | let mut pb = PathBuilder::new(); 11 | pb.move_to(10.0, 10.0); 12 | pb.cubic_to(20.0, 30.0, 120.0, 250.0, 200.0, 150.0); 13 | pb.close(); 14 | let path = pb.finish(); 15 | 16 | let src = Source::from(Color::new(200, 50, 127, 150)); 17 | 18 | let draw_opt = DrawOptions { 19 | blend_mode: BlendMode::SrcOver, 20 | alpha: 1.0, 21 | antialias: AntialiasMode::None, 22 | }; 23 | 24 | b.iter(|| { 25 | dt.fill(&path, &src, &draw_opt); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jeff Muizelaar "] 3 | edition = "2018" 4 | name = "raqote" 5 | version = "0.8.5" 6 | description = "2D graphics library" 7 | license = "BSD-3-Clause" 8 | repository = "https://github.com/jrmuizel/raqote" 9 | documentation = "https://docs.rs/raqote" 10 | readme = "README.md" 11 | keywords = ["2d", "graphics"] 12 | categories = ["graphics"] 13 | 14 | [dependencies] 15 | euclid = "0.22" 16 | font-kit = { version = "0.14", optional = true } 17 | lyon_geom = "1.0" 18 | pathfinder_geometry = { version = "0.5", optional = true } 19 | png = { version = "0.17", optional = true } 20 | typed-arena = "2.0" 21 | sw-composite = "0.7.15" 22 | 23 | [features] 24 | default = ["text", "png"] 25 | text = ["font-kit", "pathfinder_geometry"] 26 | -------------------------------------------------------------------------------- /examples/sweep-gradient.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | use raqote::*; 3 | 4 | let mut dt = DrawTarget::new(400, 400); 5 | 6 | let mut pb = PathBuilder::new(); 7 | pb.rect(0., 0., 400., 400.); 8 | let path = pb.finish(); 9 | 10 | let gradient = Source::new_sweep_gradient( 11 | Gradient { 12 | stops: vec![ 13 | GradientStop { 14 | position: 0., 15 | color: Color::new(0xff, 0, 0, 0), 16 | }, 17 | GradientStop { 18 | position: 0.5, 19 | color: Color::new(0xff, 0xff, 0xff, 0x0), 20 | }, 21 | GradientStop { 22 | position: 1., 23 | color: Color::new(0xff, 0, 0, 0x0), 24 | }, 25 | ], 26 | }, 27 | Point::new(150., 200.), 28 | 45., 29 | 180.+45., 30 | Spread::Repeat, 31 | ); 32 | dt.fill(&path, &gradient, &DrawOptions::new()); 33 | 34 | 35 | 36 | dt.write_png("example.png"); 37 | } -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --verbose 23 | semver: 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v3 27 | - name: Check semver 28 | uses: obi1kenobi/cargo-semver-checks-action@v2 29 | miri: 30 | name: "Miri" 31 | runs-on: ubuntu-latest 32 | steps: 33 | - uses: actions/checkout@v2 34 | - name: Install Miri 35 | run: | 36 | rustup toolchain install nightly --component miri 37 | rustup override set nightly 38 | cargo miri setup 39 | - name: Test with Miri 40 | run: cargo miri test --lib --bins --tests --examples 41 | -------------------------------------------------------------------------------- /examples/pad.rs: -------------------------------------------------------------------------------- 1 | extern crate raqote; 2 | 3 | use raqote::*; 4 | use std::fs::*; 5 | use sw_composite::{Gradient, GradientStop, Image}; 6 | 7 | use font_kit::family_name::FamilyName; 8 | use font_kit::properties::Properties; 9 | use font_kit::source::SystemSource; 10 | fn main() { 11 | let mut dt = DrawTarget::new(200, 200); 12 | 13 | let gradient = Source::new_linear_gradient( 14 | Gradient { 15 | stops: vec![ 16 | GradientStop { 17 | position: 0.0, 18 | color: Color::new(0xff, 0xff, 0xff, 0xff), 19 | }, 20 | GradientStop { 21 | position: 0.9999, 22 | color: Color::new(0xff, 0x0, 0x0, 0x0), 23 | }, 24 | GradientStop { 25 | position: 1.0, 26 | color: Color::new(0xff, 0x0, 0x0, 0x0), 27 | }, 28 | ], 29 | }, 30 | Point::new(40., 0.), 31 | Point::new(100., 0.), 32 | Spread::Pad, 33 | ); 34 | 35 | let mut pb = PathBuilder::new(); 36 | pb.rect(0., 0., 80., 80.); 37 | let path = pb.finish(); 38 | dt.fill(&path, &gradient, &DrawOptions::default()); 39 | 40 | dt.write_png("out.png").unwrap(); 41 | } 42 | -------------------------------------------------------------------------------- /examples/text.rs: -------------------------------------------------------------------------------- 1 | use font_kit::family_name::FamilyName; 2 | use font_kit::properties::{Properties, Weight}; 3 | use font_kit::source::SystemSource; 4 | use raqote::*; 5 | 6 | fn main() { 7 | let mut dt = DrawTarget::new(300, 100); 8 | dt.clear(SolidSource::from_unpremultiplied_argb( 9 | 0xff, 0xcf, 0xcf, 0xcf, 10 | )); 11 | 12 | let font = SystemSource::new() 13 | .select_best_match( 14 | &[FamilyName::Title("Roboto".into())], 15 | &Properties::new().weight(Weight::MEDIUM), 16 | ) 17 | .unwrap() 18 | .load() 19 | .unwrap(); 20 | println!("{:?}", font); 21 | 22 | //dt.set_transform(&Transform::create_translation(50.0, 0.0)); 23 | dt.set_transform(&Transform::rotation(euclid::Angle::degrees(15.0))); 24 | let font = font_kit::loader::Loader::from_file(&mut std::fs::File::open("res/Box3.ttf").unwrap(), 0).unwrap(); 25 | dt.draw_text( 26 | &font, 27 | 30., 28 | "3", 29 | Point::new(0., 30.), 30 | &Source::Solid(SolidSource::from_unpremultiplied_argb(255, 0, 180, 0)), 31 | &DrawOptions::new(), 32 | ); 33 | dt.fill_rect(0., 35., 40., 5., &Source::Solid(SolidSource::from_unpremultiplied_argb(255, 0, 180, 0)), 34 | &DrawOptions::new() ); 35 | 36 | dt.write_png("out.png").unwrap(); 37 | } 38 | -------------------------------------------------------------------------------- /DESIGN.md: -------------------------------------------------------------------------------- 1 | The overall design of raqote is very conservative. It contains nothing novel and is mostly 2 | a repackaging of classic techniques used elsewhere. It borrows heavily from Skia. 3 | 4 | The rasterizer is a relatively straightforward 4x4 supersampling scanline 5 | rasterizer. It includes some tricks taken from Skia: 6 | 1. Monotonic quadratic curve edges can be used directly instead of having to flatten them. 7 | 2. Partial results are accumulated directly into a scanline with some approximations to avoid overflow at 255 8 | 3. An alpha mask for the entire shape is produced and then composited. However the intention is to switch 9 | to Skia like run length representation and only shade the parts of the mask where there is coverage. 10 | 11 | The stroker is a classic postscript style stroker that works on flattened paths. It does not try 12 | avoid overlap and uses distinct subpaths for each line segment, join and cap. 13 | 14 | The dasher just chops a flattened paths into subpaths for each dash. It does 15 | not try to handle zero-length segments. 16 | 17 | The compositor is designed around shading a scanline at a time. Gradients are sampled from a lookup 18 | table and bilinear filtering is a lower precision approximation that's cheaper to compute on the cpu. 19 | 20 | Global alpha is implemented by having shaders handle it manually. 21 | 22 | Prior Art: 23 | - Skia 24 | - Cairo 25 | - Fitz 26 | - Blend2D 27 | - Qt 28 | - Libart 29 | - Antigrain 30 | - Java2D (Pisces/Marlin https://github.com/bourgesl/marlin-renderer) 31 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Jeff Muizelaar 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the copyright holder nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /src/geom.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2006 The Android Open Source Project 3 | * 4 | * Use of this source code is governed by a BSD-style license that can be 5 | * found in the LICENSE.skia file. 6 | */ 7 | use crate::Point; 8 | 9 | pub fn intrect(x1: T, y1: T, x2: T, y2: T) -> euclid::default::Box2D { 10 | euclid::default::Box2D::new(euclid::point2(x1, y1), euclid::point2(x2, y2)) 11 | } 12 | 13 | // we can do this 14 | pub fn valid_unit_divide(mut numer: f32, mut denom: f32, ratio: &mut f32) -> bool { 15 | if numer < 0. { 16 | numer = -numer; 17 | denom = -denom; 18 | } 19 | 20 | if denom == 0. || numer == 0. || numer >= denom { 21 | return false; 22 | } 23 | 24 | let r = numer / denom; 25 | if r.is_nan() { 26 | return false; 27 | } 28 | debug_assert!(r >= 0. && r < 1.); 29 | if r == 0. { 30 | // catch underflow if numer <<<< denom 31 | return false; 32 | } 33 | *ratio = r; 34 | 35 | true 36 | } 37 | 38 | pub fn is_not_monotonic(a: f32, b: f32, c: f32) -> bool { 39 | let ab = a - b; 40 | let mut bc = b - c; 41 | if ab < 0. { 42 | bc = -bc; 43 | } 44 | 45 | ab == 0. || bc < 0. 46 | } 47 | 48 | fn interp(a: f32, b: f32, t: f32) -> f32 { 49 | debug_assert!(t >= 0. && t <= 1.); 50 | a + (b - a) * t 51 | } 52 | 53 | // Skia does a weird thing where it treats arrays of points as castable to array of floats. 54 | // For now we just duplicate quad_x and quad_y 55 | fn interp_quad_x_coords(src: &[Point; 3], dst: &mut [Point; 5], t: f32) { 56 | let ab = interp(src[0].x, src[1].x, t); 57 | let bc = interp(src[1].x, src[2].x, t); 58 | 59 | dst[0].x = src[0].x; 60 | dst[1].x = ab; 61 | dst[2].x = interp(ab, bc, t); 62 | dst[3].x = bc; 63 | dst[4].x = src[2].x; 64 | } 65 | 66 | fn interp_quad_y_coords(src: &[Point; 3], dst: &mut [Point; 5], t: f32) { 67 | let ab = interp(src[0].y, src[1].y, t); 68 | let bc = interp(src[1].y, src[2].y, t); 69 | 70 | dst[0].y = src[0].y; 71 | dst[1].y = ab; 72 | dst[2].y = interp(ab, bc, t); 73 | dst[3].y = bc; 74 | dst[4].y = src[2].y; 75 | } 76 | 77 | pub fn chop_quad_at(src: &[Point; 3], dst: &mut [Point; 5], t: f32) { 78 | debug_assert!(t > 0. && t < 1.); 79 | 80 | interp_quad_x_coords(src, dst, t); 81 | interp_quad_y_coords(src, dst, t); 82 | } 83 | 84 | // ensures that the y values are contiguous 85 | // dst[1].fY = dst[3].fY = dst[2].fY 86 | // I'm not sure why we need this 87 | pub fn flatten_double_quad_extrema(dst: &mut [Point; 5]) { 88 | dst[1].y = dst[2].y; 89 | dst[3].y = dst[2].y; 90 | } 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## raqote 2 | [![Build Status](https://github.com/jrmuizel/raqote/actions/workflows/rust.yml/badge.svg)](https://github.com/jrmuizel/raqote/actions) 3 | [![crates.io](https://img.shields.io/crates/v/raqote.svg)](https://crates.io/crates/raqote) 4 | [![Documentation](https://docs.rs/raqote/badge.svg)](https://docs.rs/raqote) 5 | 6 | 7 | A pure Rust 2D Graphics Library. 8 | 9 | Raqote is a small, simple, fast software 2D graphics library. 10 | 11 | Current functionality 12 | - path filling 13 | - stroking 14 | - dashing 15 | - image, solid, and gradient fills 16 | - rectangular and path clipping 17 | - blend modes 18 | - layers 19 | - repeat modes for images 20 | - global alpha 21 | 22 | #### Notable users 23 | - [resvg](https://github.com/RazrFalcon/resvg) supports using raqote as a backend. 24 | - [Servo](https://github.com/servo/servo) uses raqote as its canvas backend. 25 | - [orbtk](https://gitlab.redox-os.org/redox-os/orbtk/tree/master/crates/render) uses raqote. 26 | 27 | Example: 28 | 29 | [A simple example drawing to a window](https://github.com/jrmuizel/raqote-examples/blob/master/examples/minifb.rs) 30 | 31 | Another example drawing to a png follows: 32 | 33 | ```rust 34 | use raqote::*; 35 | 36 | let mut dt = DrawTarget::new(400, 400); 37 | 38 | let mut pb = PathBuilder::new(); 39 | pb.move_to(100., 10.); 40 | pb.cubic_to(150., 40., 175., 0., 200., 10.); 41 | pb.quad_to(120., 100., 80., 200.); 42 | pb.quad_to(150., 180., 300., 300.); 43 | pb.close(); 44 | let path = pb.finish(); 45 | 46 | let gradient = Source::new_radial_gradient( 47 | Gradient { 48 | stops: vec![ 49 | GradientStop { 50 | position: 0.2, 51 | color: Color::new(0xff, 0, 0xff, 0), 52 | }, 53 | GradientStop { 54 | position: 0.8, 55 | color: Color::new(0xff, 0xff, 0xff, 0xff), 56 | }, 57 | GradientStop { 58 | position: 1., 59 | color: Color::new(0xff, 0xff, 0, 0xff), 60 | }, 61 | ], 62 | }, 63 | Point::new(150., 150.), 64 | 128., 65 | Spread::Pad, 66 | ); 67 | dt.fill(&path, &gradient, &DrawOptions::new()); 68 | 69 | let mut pb = PathBuilder::new(); 70 | pb.move_to(100., 100.); 71 | pb.line_to(300., 300.); 72 | pb.line_to(200., 300.); 73 | let path = pb.finish(); 74 | 75 | dt.stroke( 76 | &path, 77 | &Source::Solid(SolidSource { 78 | r: 0x0, 79 | g: 0x0, 80 | b: 0x80, 81 | a: 0x80, 82 | }), 83 | &StrokeStyle { 84 | cap: LineCap::Round, 85 | join: LineJoin::Round, 86 | width: 10., 87 | miter_limit: 2., 88 | dash_array: vec![10., 18.], 89 | dash_offset: 16., 90 | }, 91 | &DrawOptions::new() 92 | ); 93 | 94 | dt.write_png("example.png"); 95 | ``` 96 | 97 | Produces: 98 | 99 | ![example.png](https://github.com/jrmuizel/raqote/raw/master/example.png) 100 | 101 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | 3 | A pure Rust 2D Graphics Library. 4 | 5 | Raqote is a small, simple, fast software 2D graphics library. 6 | 7 | Current functionality 8 | - path filling 9 | - stroking 10 | - dashing 11 | - image, solid, and gradient fills 12 | - rectangular and path clipping 13 | - blend modes 14 | - layers 15 | - repeat modes for images 16 | - global alpha 17 | 18 | ### Notable users 19 | - [resvg](https://github.com/RazrFalcon/resvg) supports using raqote as a backend. 20 | - [Servo](https://github.com/servo/servo) uses raqote as its canvas backend. 21 | - [orbtk](https://gitlab.redox-os.org/redox-os/orbtk/tree/master/crates/render) uses raqote. 22 | 23 | Example: 24 | 25 | [A simple example drawing to a window](https://github.com/jrmuizel/raqote-examples/blob/master/examples/minifb.rs) 26 | 27 | Another example drawing to a png follows: 28 | 29 | ```rust 30 | use raqote::*; 31 | 32 | let mut dt = DrawTarget::new(400, 400); 33 | 34 | let mut pb = PathBuilder::new(); 35 | pb.move_to(100., 10.); 36 | pb.cubic_to(150., 40., 175., 0., 200., 10.); 37 | pb.quad_to(120., 100., 80., 200.); 38 | pb.quad_to(150., 180., 300., 300.); 39 | pb.close(); 40 | let path = pb.finish(); 41 | 42 | let gradient = Source::new_radial_gradient( 43 | Gradient { 44 | stops: vec![ 45 | GradientStop { 46 | position: 0.2, 47 | color: Color::new(0xff, 0, 0xff, 0), 48 | }, 49 | GradientStop { 50 | position: 0.8, 51 | color: Color::new(0xff, 0xff, 0xff, 0xff), 52 | }, 53 | GradientStop { 54 | position: 1., 55 | color: Color::new(0xff, 0xff, 0, 0xff), 56 | }, 57 | ], 58 | }, 59 | Point::new(150., 150.), 60 | 128., 61 | Spread::Pad, 62 | ); 63 | dt.fill(&path, &gradient, &DrawOptions::new()); 64 | 65 | let mut pb = PathBuilder::new(); 66 | pb.move_to(100., 100.); 67 | pb.line_to(300., 300.); 68 | pb.line_to(200., 300.); 69 | let path = pb.finish(); 70 | 71 | dt.stroke( 72 | &path, 73 | &Source::Solid(SolidSource { 74 | r: 0x0, 75 | g: 0x0, 76 | b: 0x80, 77 | a: 0x80, 78 | }), 79 | &StrokeStyle { 80 | cap: LineCap::Round, 81 | join: LineJoin::Round, 82 | width: 10., 83 | miter_limit: 2., 84 | dash_array: vec![10., 18.], 85 | dash_offset: 16., 86 | }, 87 | &DrawOptions::new() 88 | ); 89 | 90 | dt.write_png("example.png"); 91 | ``` 92 | 93 | Produces: 94 | 95 | ![example.png](https://github.com/jrmuizel/raqote/raw/master/example.png) 96 | 97 | */ 98 | 99 | #![warn(missing_copy_implementations)] 100 | 101 | mod blitter; 102 | mod dash; 103 | mod draw_target; 104 | mod geom; 105 | mod rasterizer; 106 | mod stroke; 107 | mod tests; 108 | 109 | mod path_builder; 110 | pub use path_builder::*; 111 | 112 | pub use crate::draw_target::{AntialiasMode, FilterMode}; 113 | pub use crate::draw_target::{BlendMode, DrawOptions, DrawTarget, SolidSource, Source, Winding, ExtendMode, Mask}; 114 | pub use crate::stroke::*; 115 | 116 | pub use sw_composite::{Color, Gradient, GradientStop, Image, Spread}; 117 | 118 | pub type IntRect = euclid::default::Box2D; 119 | pub type IntPoint = euclid::default::Point2D; 120 | pub type Point = euclid::default::Point2D; 121 | pub type Transform = euclid::default::Transform2D; 122 | pub type Vector = euclid::default::Vector2D; 123 | -------------------------------------------------------------------------------- /examples/capabilities.rs: -------------------------------------------------------------------------------- 1 | extern crate raqote; 2 | 3 | use raqote::*; 4 | use std::fs::*; 5 | use sw_composite::{Gradient, GradientStop, Image}; 6 | 7 | use font_kit::family_name::FamilyName; 8 | use font_kit::properties::Properties; 9 | use font_kit::source::SystemSource; 10 | 11 | fn main() { 12 | let mut dt = DrawTarget::new(400, 400); 13 | 14 | let mut pb = PathBuilder::new(); 15 | pb.move_to(340., 190.); 16 | pb.arc(160., 190., 180., 0., 2. * 3.14159); 17 | pb.close(); 18 | let path = pb.finish(); 19 | dt.push_clip(&path); 20 | 21 | let mut pb = PathBuilder::new(); 22 | pb.move_to(0., 0.); 23 | pb.line_to(200., 0.); 24 | pb.line_to(200., 300.); 25 | pb.line_to(0., 300.); 26 | pb.close(); 27 | let path = pb.finish(); 28 | dt.fill( 29 | &path, 30 | &Source::Solid(SolidSource { 31 | r: 0x80, 32 | g: 0x80, 33 | b: 0, 34 | a: 0x80, 35 | }), 36 | &DrawOptions::new(), 37 | ); 38 | 39 | let mut pb = PathBuilder::new(); 40 | pb.move_to(50., 50.); 41 | pb.line_to(100., 70.); 42 | pb.line_to(110., 150.); 43 | pb.line_to(40., 180.); 44 | pb.close(); 45 | 46 | /* 47 | dt.move_to(100., 10.); 48 | dt.quad_to(150., 40., 200., 10.); 49 | dt.quad_to(120., 100., 80., 200.); 50 | dt.quad_to(150., 180., 200., 200.); 51 | dt.close(); 52 | */ 53 | 54 | pb.move_to(100., 10.); 55 | pb.cubic_to(150., 40., 175., 0., 200., 10.); 56 | pb.quad_to(120., 100., 80., 200.); 57 | pb.quad_to(150., 180., 200., 200.); 58 | pb.close(); 59 | 60 | let path = pb.finish(); 61 | 62 | let decoder = png::Decoder::new(File::open("photo.png").unwrap()); 63 | let mut reader = decoder.read_info().unwrap(); 64 | let mut buf = vec![0; reader.output_buffer_size()]; 65 | let info = reader.next_frame(&mut buf).unwrap(); 66 | 67 | println!("{:?}", info.color_type); 68 | 69 | let mut image: Vec = Vec::new(); 70 | for i in buf.chunks(3) { 71 | image.push(0xff << 24 | ((i[0] as u32) << 16) | ((i[1] as u32) << 8) | (i[2] as u32)) 72 | } 73 | let _bitmap = Image { 74 | width: info.width as i32, 75 | height: info.height as i32, 76 | data: &image[..], 77 | }; 78 | 79 | //dt.fill(Source::Solid(SolidSource{r: 0xff, g: 0xff, b: 0, a: 0xff})); 80 | //dt.fill(Source::Bitmap(bitmap, Transform::create_scale(2., 2.))); 81 | 82 | let gradient = Source::RadialGradient( 83 | Gradient { 84 | stops: vec![ 85 | GradientStop { 86 | position: 0.2, 87 | color: Color::new(0xff, 0x00, 0xff, 0x00), 88 | }, 89 | GradientStop { 90 | position: 0.8, 91 | color: Color::new(0xff, 0xff, 0xff, 0xff), 92 | }, 93 | GradientStop { 94 | position: 1., 95 | color: Color::new(0xff, 0xff, 0x00, 0xff), 96 | }, 97 | ], 98 | }, 99 | Spread::Pad, 100 | Transform::translation(-150., -150.), 101 | ); 102 | dt.fill(&path, &gradient, &DrawOptions::new()); 103 | 104 | let mut pb = PathBuilder::new(); 105 | pb.move_to(200., 200.); 106 | pb.line_to(300., 300.); 107 | pb.line_to(200., 300.); 108 | 109 | let path = pb.finish(); 110 | dt.stroke( 111 | &path, 112 | &gradient, 113 | &StrokeStyle { 114 | cap: LineCap::Butt, 115 | join: LineJoin::Bevel, 116 | width: 10., 117 | miter_limit: 2., 118 | dash_array: vec![10., 5.], 119 | dash_offset: 3., 120 | }, 121 | &DrawOptions::new(), 122 | ); 123 | 124 | let font = SystemSource::new() 125 | .select_best_match(&[FamilyName::SansSerif], &Properties::new()) 126 | .unwrap() 127 | .load() 128 | .unwrap(); 129 | 130 | dt.draw_text( 131 | &font, 132 | 24., 133 | "Hello", 134 | Point::new(0., 100.), 135 | &Source::Solid(SolidSource { 136 | r: 0, 137 | g: 0, 138 | b: 0xff, 139 | a: 0xff, 140 | }), 141 | &DrawOptions::new(), 142 | ); 143 | 144 | dt.write_png("out.png").unwrap(); 145 | } 146 | -------------------------------------------------------------------------------- /src/dash.rs: -------------------------------------------------------------------------------- 1 | use crate::path_builder::*; 2 | 3 | use crate::Point; 4 | 5 | use lyon_geom::LineSegment; 6 | 7 | #[derive(Clone, Copy)] 8 | struct DashState { 9 | index: usize, // index into the dash array 10 | on: bool, // whether the dash is on or off 11 | remaining_length: f32, // how much of the dash remains 12 | } 13 | 14 | pub fn dash_path(path: &Path, dash_array: &[f32], mut dash_offset: f32) -> Path { 15 | let mut dashed = PathBuilder::new(); 16 | 17 | let mut cur_pt = None; 18 | let mut start_point = None; 19 | 20 | let mut total_dash_length = 0.; 21 | for dash in dash_array { 22 | total_dash_length += *dash; 23 | } 24 | if dash_array.len() % 2 == 1 { 25 | // if the number of items in the dash_array is odd then we need it takes two periods 26 | // to return to the beginning. 27 | total_dash_length *= 2.; 28 | } 29 | 30 | // The dash length must be more than zero. 31 | if !(total_dash_length > 0.) { 32 | return dashed.finish(); 33 | } 34 | 35 | // Handle large positive and negative offsets so that we don't loop for a high number of 36 | // iterations below in extreme cases 37 | dash_offset = dash_offset % total_dash_length; 38 | if dash_offset < 0. { 39 | dash_offset += total_dash_length; 40 | } 41 | 42 | // To handle closed paths we need a bunch of extra state so that we properly 43 | // join the first segment. Unfortunately, this makes the code sort of hairy. 44 | // We need to store all of the points in the initial segment so that we can 45 | // join the end of the path with it. 46 | let mut is_first_segment = true; 47 | let mut first_dash = true; 48 | let mut initial_segment : Vec = Vec::new(); 49 | 50 | let mut state = DashState { 51 | on: true, 52 | remaining_length: dash_array[0], 53 | index: 0, 54 | }; 55 | 56 | // adjust our position in the dash array by the dash offset 57 | while dash_offset > state.remaining_length { 58 | dash_offset -= state.remaining_length; 59 | state.index += 1; 60 | state.remaining_length = dash_array[state.index % dash_array.len()]; 61 | state.on = !state.on; 62 | } 63 | state.remaining_length -= dash_offset; 64 | 65 | // Save a copy of the initial state so that we can restore it for each subpath 66 | let initial = state; 67 | for op in &path.ops { 68 | match *op { 69 | PathOp::MoveTo(pt) => { 70 | cur_pt = Some(pt); 71 | start_point = Some(pt); 72 | dashed.move_to(pt.x, pt.y); 73 | 74 | // flush the previous initial segment 75 | if initial_segment.len() > 0 { 76 | dashed.move_to(initial_segment[0].x, initial_segment[0].y); 77 | for i in 1..initial_segment.len() { 78 | dashed.line_to(initial_segment[i].x, initial_segment[i].y); 79 | } 80 | } 81 | is_first_segment = true; 82 | initial_segment = Vec::new(); 83 | first_dash = true; 84 | 85 | // reset the dash state 86 | state = initial; 87 | } 88 | PathOp::LineTo(pt) => { 89 | if let Some(cur_pt) = cur_pt { 90 | let mut start = cur_pt; 91 | let line = LineSegment { 92 | from: start, 93 | to: pt, 94 | }; 95 | let mut len = line.length(); 96 | let lv = line.to_vector().normalize(); 97 | while len > state.remaining_length { 98 | let seg = start + lv * state.remaining_length; 99 | if state.on { 100 | if is_first_segment { 101 | initial_segment.push(start); 102 | initial_segment.push(seg); 103 | } else { 104 | dashed.line_to(seg.x, seg.y); 105 | } 106 | } else { 107 | first_dash = false; 108 | dashed.move_to(seg.x, seg.y); 109 | } 110 | is_first_segment = false; 111 | state.on = !state.on; 112 | state.index += 1; 113 | len -= state.remaining_length; 114 | state.remaining_length = dash_array[state.index % dash_array.len()]; 115 | start = seg; 116 | } 117 | if state.on { 118 | if is_first_segment { 119 | initial_segment.push(start); 120 | initial_segment.push(pt); 121 | } else { 122 | dashed.line_to(pt.x, pt.y); 123 | } 124 | } else { 125 | first_dash = false; 126 | dashed.move_to(pt.x, pt.y); 127 | } 128 | state.remaining_length -= len; 129 | } 130 | cur_pt = Some(pt); 131 | } 132 | PathOp::Close => { 133 | if let (Some(current), Some(start_point)) = (cur_pt, start_point) { 134 | let mut start = current; 135 | let line = LineSegment { 136 | from: start, 137 | to: start_point, 138 | }; 139 | let mut len = line.length(); 140 | let lv = line.to_vector().normalize(); 141 | 142 | while len > state.remaining_length { 143 | let seg = start + lv * state.remaining_length; 144 | if state.on { 145 | if is_first_segment { 146 | initial_segment.push(start); 147 | initial_segment.push(seg); 148 | } else { 149 | dashed.line_to(seg.x, seg.y); 150 | } 151 | } else { 152 | first_dash = false; 153 | dashed.move_to(seg.x, seg.y); 154 | } 155 | state.on = !state.on; 156 | state.index += 1; 157 | len -= state.remaining_length; 158 | state.remaining_length = dash_array[state.index % dash_array.len()]; 159 | start = seg; 160 | } 161 | 162 | if state.on { 163 | if first_dash { 164 | // If we're still on the first dash we can just close 165 | dashed.close(); 166 | } else { 167 | if initial_segment.len() > 0 { 168 | // If have an initial segment we'll need to connect with it 169 | for pt in initial_segment { 170 | dashed.line_to(pt.x, pt.y); 171 | } 172 | } else { 173 | dashed.line_to(start_point.x, start_point.y); 174 | } 175 | } 176 | } else { 177 | if initial_segment.len() > 0 { 178 | dashed.move_to(initial_segment[0].x, initial_segment[0].y); 179 | for i in 1..initial_segment.len() { 180 | dashed.line_to(initial_segment[i].x, initial_segment[i].y); 181 | } 182 | } 183 | } 184 | initial_segment = Vec::new(); 185 | cur_pt = Some(start_point); 186 | 187 | // reset the dash state 188 | state = initial; 189 | } else { 190 | cur_pt = None; 191 | } 192 | } 193 | PathOp::QuadTo(..) => panic!("Only flat paths handled"), 194 | PathOp::CubicTo(..) => panic!("Only flat paths handled"), 195 | } 196 | } 197 | 198 | // We still have an initial segment that we need to emit 199 | if !initial_segment.is_empty() { 200 | dashed.move_to(initial_segment[0].x, initial_segment[0].y); 201 | for i in 1..initial_segment.len() { 202 | dashed.line_to(initial_segment[i].x, initial_segment[i].y); 203 | } 204 | } 205 | dashed.finish() 206 | } 207 | -------------------------------------------------------------------------------- /src/path_builder.rs: -------------------------------------------------------------------------------- 1 | use lyon_geom::Angle; 2 | use lyon_geom::Arc; 3 | use lyon_geom::CubicBezierSegment; 4 | use lyon_geom::QuadraticBezierSegment; 5 | 6 | use crate::{Point, Transform, Vector}; 7 | 8 | #[derive(Clone, Copy, PartialEq, Debug)] 9 | pub enum Winding { 10 | EvenOdd, 11 | NonZero, 12 | } 13 | 14 | #[derive(Clone, Copy, Debug)] 15 | pub enum PathOp { 16 | MoveTo(Point), 17 | LineTo(Point), 18 | QuadTo(Point, Point), 19 | CubicTo(Point, Point, Point), 20 | Close, 21 | } 22 | 23 | impl PathOp { 24 | fn transform(self, xform: &Transform) -> PathOp { 25 | match self { 26 | PathOp::MoveTo(p) => PathOp::MoveTo(xform.transform_point(p)), 27 | PathOp::LineTo(p) => PathOp::LineTo(xform.transform_point(p)), 28 | PathOp::QuadTo(p1, p2) => PathOp::QuadTo( 29 | xform.transform_point(p1), 30 | xform.transform_point(p2) 31 | ), 32 | PathOp::CubicTo(p1, p2, p3) => PathOp::CubicTo( 33 | xform.transform_point(p1), 34 | xform.transform_point(p2), 35 | xform.transform_point(p3), 36 | ), 37 | PathOp::Close => PathOp::Close, 38 | } 39 | } 40 | } 41 | 42 | /// Represents a complete path usable for filling or stroking. 43 | #[derive(Clone, Debug)] 44 | pub struct Path { 45 | pub ops: Vec, 46 | pub winding: Winding, 47 | } 48 | 49 | impl Path { 50 | /// Flattens `self` by replacing all QuadTo and CurveTo 51 | /// commands with an appropriate number of LineTo commands 52 | /// so that the error is not greater than `tolerance`. 53 | pub fn flatten(&self, tolerance: f32) -> Path { 54 | let mut cur_pt = None; 55 | let mut flattened = Path { ops: Vec::new(), winding: Winding::NonZero }; 56 | for op in &self.ops { 57 | match *op { 58 | PathOp::MoveTo(pt) | PathOp::LineTo(pt) => { 59 | cur_pt = Some(pt); 60 | flattened.ops.push(op.clone()) 61 | } 62 | PathOp::Close => { 63 | cur_pt = None; 64 | flattened.ops.push(op.clone()) 65 | } 66 | PathOp::QuadTo(cpt, pt) => { 67 | let start = cur_pt.unwrap_or(cpt); 68 | let c = QuadraticBezierSegment { 69 | from: start, 70 | ctrl: cpt, 71 | to: pt, 72 | }; 73 | for l in c.flattened(tolerance) { 74 | flattened.ops.push(PathOp::LineTo(l)); 75 | } 76 | cur_pt = Some(pt); 77 | } 78 | PathOp::CubicTo(cpt1, cpt2, pt) => { 79 | let start = cur_pt.unwrap_or(cpt1); 80 | let c = CubicBezierSegment { 81 | from: start, 82 | ctrl1: cpt1, 83 | ctrl2: cpt2, 84 | to: pt, 85 | }; 86 | for l in c.flattened(tolerance) { 87 | flattened.ops.push(PathOp::LineTo(l)); 88 | } 89 | cur_pt = Some(pt); 90 | } 91 | } 92 | } 93 | flattened 94 | } 95 | 96 | /// Returns true if the point `x`, `y` is within the filled 97 | /// area of of `self`. The path will be flattened using `tolerance`. 98 | /// The point is considered contained if it's on the path. 99 | // this function likely has bugs 100 | pub fn contains_point(&self, tolerance: f32, x: f32, y: f32) -> bool { 101 | //XXX Instead of making a new path we should just use flattening callbacks 102 | let flat_path = self.flatten(tolerance); 103 | struct WindState { 104 | first_point: Option, 105 | current_point: Option, 106 | count: i32, 107 | on_edge: bool, 108 | 109 | x: f32, 110 | y: f32, 111 | } 112 | 113 | impl WindState { 114 | fn close(&mut self) { 115 | if let (Some(first_point), Some(current_point)) = (self.first_point, self.current_point) { 116 | self.add_edge( 117 | current_point, 118 | first_point, 119 | ); 120 | } 121 | self.first_point = None; 122 | } 123 | 124 | // to determine containment we just need to count crossing of ray from (x, y) going to infinity 125 | fn add_edge(&mut self, p1: Point, p2: Point) { 126 | let (x1, y1) = (p1.x, p1.y); 127 | let (x2, y2) = (p2.x, p2.y); 128 | 129 | let dir = if y1 < y2 { -1 } else { 1 }; 130 | 131 | // entirely to the right 132 | if x1 > self.x && x2 > self.x { 133 | return 134 | } 135 | 136 | // entirely above 137 | if y1 > self.y && y2 > self.y { 138 | return 139 | } 140 | 141 | // entirely below 142 | if y1 < self.y && y2 < self.y { 143 | return 144 | } 145 | 146 | // entirely to the left 147 | if x1 < self.x && x2 < self.x { 148 | if y1 > self.y && y2 < self.y { 149 | self.count += 1; 150 | return; 151 | } 152 | if y2 > self.y && y1 < self.y { 153 | self.count -= 1; 154 | return; 155 | } 156 | } 157 | 158 | let dx = x2 - x1; 159 | let dy = y2 - y1; 160 | 161 | // cross product/perp dot product lets us know which side of the line we're on 162 | let cross = dx * (self.y - y1) - dy * (self.x - x1); 163 | 164 | if cross == 0. { 165 | self.on_edge = true; 166 | } else if (cross > 0. && dir > 0) || (cross < 0. && dir < 0) { 167 | self.count += dir; 168 | } 169 | } 170 | } 171 | 172 | let mut ws = WindState { count: 0, first_point: None, current_point: None, x, y, on_edge: false}; 173 | 174 | for op in &flat_path.ops { 175 | match *op { 176 | PathOp::MoveTo(pt) => { 177 | ws.close(); 178 | ws.current_point = Some(pt); 179 | ws.first_point = Some(pt); 180 | }, 181 | PathOp::LineTo(pt) => { 182 | if let Some(current_point) = ws.current_point { 183 | ws.add_edge(current_point, pt); 184 | } else { 185 | ws.first_point = Some(pt); 186 | } 187 | ws.current_point = Some(pt); 188 | }, 189 | PathOp::QuadTo(..) | 190 | PathOp::CubicTo(..) => panic!(), 191 | PathOp::Close => ws.close(), 192 | } 193 | } 194 | // make sure the path is closed 195 | ws.close(); 196 | 197 | let inside = match self.winding { 198 | Winding::EvenOdd => ws.count & 1 != 0, 199 | Winding::NonZero => ws.count != 0, 200 | }; 201 | inside || ws.on_edge 202 | } 203 | 204 | pub fn transform(self, transform: &Transform) -> Path { 205 | let Path { ops, winding } = self; 206 | let ops = ops.into_iter().map(|op| op.transform(transform)).collect(); 207 | Path { ops, winding } 208 | } 209 | } 210 | 211 | /// A helper struct used for constructing a `Path`. 212 | pub struct PathBuilder { 213 | path: Path, 214 | } 215 | 216 | impl From for PathBuilder { 217 | fn from(path: Path) -> Self { 218 | PathBuilder { 219 | path 220 | } 221 | } 222 | } 223 | 224 | impl PathBuilder { 225 | pub fn new() -> PathBuilder { 226 | PathBuilder { 227 | path: Path { 228 | ops: Vec::new(), 229 | winding: Winding::NonZero, 230 | }, 231 | } 232 | } 233 | 234 | /// Moves the current point to `x`, `y` 235 | pub fn move_to(&mut self, x: f32, y: f32) { 236 | self.path.ops.push(PathOp::MoveTo(Point::new(x, y))) 237 | } 238 | 239 | /// Adds a line segment from the current point to `x`, `y` 240 | pub fn line_to(&mut self, x: f32, y: f32) { 241 | self.path.ops.push(PathOp::LineTo(Point::new(x, y))) 242 | } 243 | 244 | /// Adds a quadratic bezier from the current point to `x`, `y`, 245 | /// using a control point of `cx`, `cy` 246 | pub fn quad_to(&mut self, cx: f32, cy: f32, x: f32, y: f32) { 247 | self.path 248 | .ops 249 | .push(PathOp::QuadTo(Point::new(cx, cy), Point::new(x, y))) 250 | } 251 | 252 | /// Adds a rect to the path 253 | pub fn rect(&mut self, x: f32, y: f32, width: f32, height: f32) { 254 | self.move_to(x, y); 255 | self.line_to(x + width, y); 256 | self.line_to(x + width, y + height); 257 | self.line_to(x, y + height); 258 | self.close(); 259 | } 260 | 261 | /// Adds a cubic bezier from the current point to `x`, `y`, 262 | /// using control points `cx1`, `cy1` and `cx2`, `cy2` 263 | pub fn cubic_to(&mut self, cx1: f32, cy1: f32, cx2: f32, cy2: f32, x: f32, y: f32) { 264 | self.path.ops.push(PathOp::CubicTo( 265 | Point::new(cx1, cy1), 266 | Point::new(cx2, cy2), 267 | Point::new(x, y), 268 | )) 269 | } 270 | 271 | /// Closes the current subpath 272 | pub fn close(&mut self) { 273 | self.path.ops.push(PathOp::Close) 274 | } 275 | 276 | 277 | /// Adds an arc approximated by quadratic beziers with center `x`, `y` 278 | /// and radius `r` starting at `start_angle` and sweeping by `sweep_angle`. 279 | /// For a positive `sweep_angle` the sweep is done clockwise, for a negative 280 | /// `sweep_angle` the sweep is done counterclockwise. 281 | pub fn arc(&mut self, x: f32, y: f32, r: f32, start_angle: f32, sweep_angle: f32) { 282 | //XXX: handle the current point being the wrong spot 283 | let a: Arc = Arc { 284 | center: Point::new(x, y), 285 | radii: Vector::new(r, r), 286 | start_angle: Angle::radians(start_angle), 287 | sweep_angle: Angle::radians(sweep_angle), 288 | x_rotation: Angle::zero(), 289 | }; 290 | let start = a.from(); 291 | self.line_to(start.x, start.y); 292 | a.for_each_quadratic_bezier(&mut |q| { 293 | self.quad_to(q.ctrl.x, q.ctrl.y, q.to.x, q.to.y); 294 | }); 295 | } 296 | 297 | /// Completes the current path 298 | pub fn finish(self) -> Path { 299 | self.path 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /src/stroke.rs: -------------------------------------------------------------------------------- 1 | // This is a simple path stroker. It flattens the path and strokes each segment individually. 2 | // For a recent survey of stroking approaches see "Converting stroked primitives to filled primitives" by Diego Nehab 3 | 4 | use crate::path_builder::{Path, PathBuilder, PathOp}; 5 | use crate::{Point, Vector}; 6 | 7 | #[derive(Clone, PartialEq, Debug)] 8 | pub struct StrokeStyle { 9 | pub width: f32, 10 | pub cap: LineCap, 11 | pub join: LineJoin, 12 | pub miter_limit: f32, 13 | pub dash_array: Vec, 14 | pub dash_offset: f32, 15 | } 16 | 17 | impl Default for StrokeStyle { 18 | fn default() -> Self { 19 | StrokeStyle { 20 | width: 1., 21 | cap: LineCap::Butt, 22 | join: LineJoin::Miter, 23 | miter_limit: 10., 24 | dash_array: Vec::new(), 25 | dash_offset: 0., 26 | } 27 | } 28 | } 29 | 30 | #[derive(Clone, Copy, PartialEq, Debug)] 31 | pub enum LineCap { 32 | Round, 33 | Square, 34 | Butt, 35 | } 36 | 37 | #[derive(Clone, Copy, PartialEq, Debug)] 38 | pub enum LineJoin { 39 | Round, 40 | Miter, 41 | Bevel, 42 | } 43 | 44 | fn compute_normal(p0: Point, p1: Point) -> Option { 45 | let ux = p1.x - p0.x; 46 | let uy = p1.y - p0.y; 47 | 48 | // this could overflow f32. Skia in SkPoint::Normalize used to 49 | // checks for this and used a double in that situation, but was 50 | // simplified to always use doubles. 51 | let ulen = ux.hypot(uy); 52 | if ulen == 0. { 53 | return None; 54 | } 55 | // the normal is perpendicular to the *unit* vector 56 | Some(Vector::new(-uy / ulen, ux / ulen)) 57 | } 58 | 59 | fn flip(v: Vector) -> Vector { 60 | Vector::new(-v.x, -v.y) 61 | } 62 | 63 | /* Compute a spline approximation of the arc 64 | centered at xc, yc from the angle a to the angle b 65 | 66 | The angle between a and b should not be more than a 67 | quarter circle (pi/2) 68 | 69 | The approximation is similar to an approximation given in: 70 | "Approximation of a cubic bezier curve by circular arcs and vice versa" 71 | by Alekas Riškus. However that approximation becomes unstable when the 72 | angle of the arc approaches 0. 73 | 74 | This approximation is inspired by a discussion with Boris Zbarsky 75 | and essentially just computes: 76 | 77 | h = 4.0/3.0 * tan ((angle_B - angle_A) / 4.0); 78 | 79 | without converting to polar coordinates. 80 | 81 | A different way to do this is covered in "Approximation of a cubic bezier 82 | curve by circular arcs and vice versa" by Alekas Riškus. However, the method 83 | presented there doesn't handle arcs with angles close to 0 because it 84 | divides by the perp dot product of the two angle vectors. 85 | */ 86 | fn arc_segment(path: &mut PathBuilder, xc: f32, yc: f32, radius: f32, a: Vector, b: Vector) { 87 | let r_sin_a = radius * a.y; 88 | let r_cos_a = radius * a.x; 89 | let r_sin_b = radius * b.y; 90 | let r_cos_b = radius * b.x; 91 | 92 | /* bisect the angle between 'a' and 'b' with 'mid' */ 93 | let mut mid = a + b; 94 | mid /= mid.length(); 95 | 96 | /* bisect the angle between 'a' and 'mid' with 'mid2' this is parallel to a 97 | * line with angle (B - A)/4 */ 98 | let mid2 = a + mid; 99 | 100 | let h = (4. / 3.) * dot(perp(a), mid2) / dot(a, mid2); 101 | 102 | path.cubic_to( 103 | xc + r_cos_a - h * r_sin_a, 104 | yc + r_sin_a + h * r_cos_a, 105 | xc + r_cos_b + h * r_sin_b, 106 | yc + r_sin_b - h * r_cos_b, 107 | xc + r_cos_b, 108 | yc + r_sin_b, 109 | ); 110 | } 111 | 112 | /* The angle between the vectors must be <= pi */ 113 | fn bisect(a: Vector, b: Vector) -> Vector { 114 | let mut mid; 115 | if dot(a, b) >= 0. { 116 | /* if the angle between a and b is accute, then we can 117 | * just add the vectors and normalize */ 118 | mid = a + b; 119 | } else { 120 | /* otherwise, we can flip a, add it 121 | * and then use the perpendicular of the result */ 122 | mid = flip(a) + b; 123 | mid = perp(mid); 124 | } 125 | 126 | /* normalize */ 127 | /* because we assume that 'a' and 'b' are normalized, we can use 128 | * sqrt instead of hypot because the range of mid is limited */ 129 | let mid_len = mid.x * mid.x + mid.y * mid.y; 130 | let len = mid_len.sqrt(); 131 | return mid / len; 132 | } 133 | 134 | fn arc(path: &mut PathBuilder, xc: f32, yc: f32, radius: f32, a: Vector, b: Vector) { 135 | /* find a vector that bisects the angle between a and b */ 136 | let mid_v = bisect(a, b); 137 | 138 | /* construct the arc using two curve segments */ 139 | arc_segment(path, xc, yc, radius, a, mid_v); 140 | arc_segment(path, xc, yc, radius, mid_v, b); 141 | } 142 | 143 | fn join_round(path: &mut PathBuilder, center: Point, a: Vector, b: Vector, radius: f32) { 144 | /* 145 | int ccw = dot (perp (b), a) >= 0; // XXX: is this always true? 146 | yes, otherwise we have an interior angle. 147 | assert (ccw); 148 | */ 149 | arc(path, center.x, center.y, radius, a, b); 150 | } 151 | 152 | fn cap_line(dest: &mut PathBuilder, style: &StrokeStyle, pt: Point, normal: Vector) { 153 | let offset = style.width / 2.; 154 | match style.cap { 155 | LineCap::Butt => { /* nothing to do */ } 156 | LineCap::Round => { 157 | dest.move_to(pt.x + normal.x * offset, pt.y + normal.y * offset); 158 | arc(dest, pt.x, pt.y, offset, normal, flip(normal)); 159 | dest.line_to(pt.x, pt.y); 160 | dest.close(); 161 | } 162 | LineCap::Square => { 163 | // parallel vector 164 | let v = Vector::new(normal.y, -normal.x); 165 | let end = pt + v * offset; 166 | dest.move_to(pt.x + normal.x * offset, pt.y + normal.y * offset); 167 | dest.line_to(end.x + normal.x * offset, end.y + normal.y * offset); 168 | dest.line_to(end.x + -normal.x * offset, end.y + -normal.y * offset); 169 | dest.line_to(pt.x - normal.x * offset, pt.y - normal.y * offset); 170 | dest.line_to(pt.x, pt.y); 171 | dest.close(); 172 | } 173 | } 174 | } 175 | 176 | fn bevel( 177 | dest: &mut PathBuilder, 178 | style: &StrokeStyle, 179 | pt: Point, 180 | s1_normal: Vector, 181 | s2_normal: Vector, 182 | ) { 183 | let offset = style.width / 2.; 184 | dest.move_to(pt.x + s1_normal.x * offset, pt.y + s1_normal.y * offset); 185 | dest.line_to(pt.x + s2_normal.x * offset, pt.y + s2_normal.y * offset); 186 | dest.line_to(pt.x, pt.y); 187 | dest.close(); 188 | } 189 | 190 | /* given a normal rotate the vector 90 degrees to the right clockwise 191 | * This function has a period of 4. e.g. swap(swap(swap(swap(x) == x */ 192 | fn swap(a: Vector) -> Vector { 193 | /* one of these needs to be negative. We choose a.x so that we rotate to the right instead of negating */ 194 | Vector::new(a.y, -a.x) 195 | } 196 | 197 | fn unperp(a: Vector) -> Vector { 198 | swap(a) 199 | } 200 | 201 | /* rotate a vector 90 degrees to the left */ 202 | fn perp(v: Vector) -> Vector { 203 | Vector::new(-v.y, v.x) 204 | } 205 | 206 | fn dot(a: Vector, b: Vector) -> f32 { 207 | a.x * b.x + a.y * b.y 208 | } 209 | 210 | /* Finds the intersection of two lines each defined by a point and a normal. 211 | From "Example 2: Find the intersection of two lines" of 212 | "The Pleasures of "Perp Dot" Products" 213 | F. S. Hill, Jr. */ 214 | fn line_intersection(a: Point, a_perp: Vector, b: Point, b_perp: Vector) -> Option { 215 | let a_parallel = unperp(a_perp); 216 | let c = b - a; 217 | let denom = dot(b_perp, a_parallel); 218 | if denom == 0.0 { 219 | return None; 220 | } 221 | 222 | let t = dot(b_perp, c) / denom; 223 | 224 | let intersection = Point::new(a.x + t * (a_parallel.x), a.y + t * (a_parallel.y)); 225 | 226 | Some(intersection) 227 | } 228 | 229 | fn is_interior_angle(a: Vector, b: Vector) -> bool { 230 | /* angles of 180 and 0 degrees will evaluate to 0, however 231 | * we to treat 180 as an interior angle and 180 as an exterior angle */ 232 | dot(perp(a), b) > 0. || a == b /* 0 degrees is interior */ 233 | } 234 | 235 | fn join_line( 236 | dest: &mut PathBuilder, 237 | style: &StrokeStyle, 238 | pt: Point, 239 | mut s1_normal: Vector, 240 | mut s2_normal: Vector, 241 | ) { 242 | if is_interior_angle(s1_normal, s2_normal) { 243 | s2_normal = flip(s2_normal); 244 | s1_normal = flip(s1_normal); 245 | std::mem::swap(&mut s1_normal, &mut s2_normal); 246 | } 247 | 248 | // XXX: joining uses `pt` which can cause seams because it lies halfway on a line and the 249 | // rasterizer may not find exactly the same spot 250 | let offset = style.width / 2.; 251 | match style.join { 252 | LineJoin::Round => { 253 | dest.move_to(pt.x + s1_normal.x * offset, pt.y + s1_normal.y * offset); 254 | join_round(dest, pt, s1_normal, s2_normal, offset); 255 | dest.line_to(pt.x, pt.y); 256 | dest.close(); 257 | } 258 | LineJoin::Miter => { 259 | let in_dot_out = -s1_normal.x * s2_normal.x + -s1_normal.y * s2_normal.y; 260 | if 2. <= style.miter_limit * style.miter_limit * (1. - in_dot_out) { 261 | let start = pt + s1_normal * offset; 262 | let end = pt + s2_normal * offset; 263 | if let Some(intersection) = line_intersection(start, s1_normal, end, s2_normal) { 264 | // We won't have an intersection if the segments are parallel 265 | dest.move_to(pt.x + s1_normal.x * offset, pt.y + s1_normal.y * offset); 266 | dest.line_to(intersection.x, intersection.y); 267 | dest.line_to(pt.x + s2_normal.x * offset, pt.y + s2_normal.y * offset); 268 | dest.line_to(pt.x, pt.y); 269 | dest.close(); 270 | } 271 | } else { 272 | bevel(dest, style, pt, s1_normal, s2_normal); 273 | } 274 | } 275 | LineJoin::Bevel => { 276 | bevel(dest, style, pt, s1_normal, s2_normal); 277 | } 278 | } 279 | } 280 | 281 | pub fn stroke_to_path(path: &Path, style: &StrokeStyle) -> Path { 282 | let mut stroked_path = PathBuilder::new(); 283 | 284 | if style.width <= 0. { 285 | return stroked_path.finish(); 286 | } 287 | 288 | let mut cur_pt = None; 289 | let mut last_normal = Vector::zero(); 290 | let half_width = style.width / 2.; 291 | let mut start_point = None; 292 | for op in &path.ops { 293 | match *op { 294 | PathOp::MoveTo(pt) => { 295 | if let (Some(cur_pt), Some((point, normal))) = (cur_pt, start_point) { 296 | // cap end 297 | cap_line(&mut stroked_path, style, cur_pt, last_normal); 298 | // cap beginning 299 | cap_line(&mut stroked_path, style, point, flip(normal)); 300 | } 301 | start_point = None; 302 | cur_pt = Some(pt); 303 | } 304 | PathOp::LineTo(pt) => { 305 | if cur_pt.is_none() { 306 | start_point = None; 307 | } else if let Some(cur_pt) = cur_pt { 308 | if let Some(normal) = compute_normal(cur_pt, pt) { 309 | if start_point.is_none() { 310 | start_point = Some((cur_pt, normal)); 311 | } else { 312 | join_line(&mut stroked_path, style, cur_pt, last_normal, normal); 313 | } 314 | 315 | stroked_path.move_to( 316 | cur_pt.x + normal.x * half_width, 317 | cur_pt.y + normal.y * half_width, 318 | ); 319 | stroked_path.line_to(pt.x + normal.x * half_width, pt.y + normal.y * half_width); 320 | // we add a point at the midpoint of the line so that our edge has matching 321 | // end points with the edges used for joining. This avoids seams during 322 | // rasterization caused by precision differences in the slope and endpoints 323 | stroked_path.line_to(pt.x, pt.y); 324 | stroked_path.line_to(pt.x + -normal.x * half_width, pt.y + -normal.y * half_width); 325 | stroked_path.line_to( 326 | cur_pt.x - normal.x * half_width, 327 | cur_pt.y - normal.y * half_width, 328 | ); 329 | stroked_path.line_to(cur_pt.x, cur_pt.y); 330 | 331 | stroked_path.close(); 332 | 333 | last_normal = normal; 334 | 335 | } 336 | } 337 | cur_pt = Some(pt); 338 | 339 | } 340 | PathOp::Close => { 341 | if let (Some(cur_pt), Some((end_point, start_normal))) = (cur_pt, start_point) { 342 | if let Some(normal) = compute_normal(cur_pt, end_point) { 343 | join_line(&mut stroked_path, style, cur_pt, last_normal, normal); 344 | 345 | // the closing line segment 346 | stroked_path.move_to( 347 | cur_pt.x + normal.x * half_width, 348 | cur_pt.y + normal.y * half_width, 349 | ); 350 | stroked_path.line_to( 351 | end_point.x + normal.x * half_width, 352 | end_point.y + normal.y * half_width, 353 | ); 354 | stroked_path.line_to( 355 | end_point.x, 356 | end_point.y, 357 | ); 358 | stroked_path.line_to( 359 | end_point.x + -normal.x * half_width, 360 | end_point.y + -normal.y * half_width, 361 | ); 362 | stroked_path.line_to( 363 | cur_pt.x - normal.x * half_width, 364 | cur_pt.y - normal.y * half_width, 365 | ); 366 | stroked_path.line_to( 367 | cur_pt.x, 368 | cur_pt.y, 369 | ); 370 | stroked_path.close(); 371 | 372 | join_line(&mut stroked_path, style, end_point, normal, start_normal); 373 | } else { 374 | join_line(&mut stroked_path, style, end_point, last_normal, start_normal); 375 | } 376 | } 377 | cur_pt = start_point.map(|x| x.0); 378 | start_point = None; 379 | } 380 | PathOp::QuadTo(..) => panic!("Only flat paths handled"), 381 | PathOp::CubicTo(..) => panic!("Only flat paths handled"), 382 | } 383 | } 384 | if let (Some(cur_pt), Some((point, normal))) = (cur_pt, start_point) { 385 | // cap end 386 | cap_line(&mut stroked_path, style, cur_pt, last_normal); 387 | // cap beginning 388 | cap_line(&mut stroked_path, style, point, flip(normal)); 389 | } 390 | stroked_path.finish() 391 | } 392 | -------------------------------------------------------------------------------- /src/rasterizer.rs: -------------------------------------------------------------------------------- 1 | /* Copyright 2013 Jeff Muizelaar 2 | * 3 | * Use of this source code is governed by a MIT-style license that can be 4 | * found in the LICENSE file. 5 | * 6 | * Portions Copyright 2006 The Android Open Source Project 7 | * 8 | * Use of that source code is governed by a BSD-style license that can be 9 | * found in the LICENSE.skia file. 10 | */ 11 | 12 | 13 | use typed_arena::Arena; 14 | 15 | use crate::{IntRect, Point}; 16 | use crate::blitter::RasterBlitter; 17 | use crate::path_builder::Winding; 18 | use crate::geom::intrect; 19 | 20 | use std::ptr::NonNull; 21 | 22 | // One reason to have separate Edge/ActiveEdge is reduce the 23 | // memory usage of inactive edges. On the other hand 24 | // managing the lifetime of ActiveEdges is a lot 25 | // trickier than Edges. Edges can stay alive for the entire 26 | // rasterization. ActiveEdges will come and go in a much 27 | // less predictable order. On the other hand having the 28 | // ActiveEdges close together in memory would help 29 | // avoid cache misses. If we did switch to having separate 30 | // active edges it might be wise to store the active edges 31 | // in an array instead of as a linked list. This will work 32 | // well for the bubble sorting, but will cause more problems 33 | // for insertion. 34 | 35 | struct Edge { 36 | //XXX: it is probably worth renaming this to top and bottom 37 | x1: Dot2, 38 | y1: Dot2, 39 | x2: Dot2, 40 | y2: Dot2, 41 | control_x: Dot2, 42 | control_y: Dot2, 43 | } 44 | 45 | // Fixed point representation: 46 | // We use 30.2 for the end points and 16.16 for the intermediate 47 | // results. I believe this essentially limits us to a 16.16 space. 48 | // 49 | // Prior Work: 50 | // - Cairo used to be 16.16 but switched to 24.8. Cairo converts paths 51 | // early to this fixed representation 52 | // - Fitz uses different precision depending on the AA settings 53 | // and uses the following Bresenham style adjustment in its step function 54 | // to avoid having to worry about intermediate precision 55 | // edge->x += edge->xmove; 56 | // edge->e += edge->adj_up; 57 | // if (edge->e > 0) { 58 | // edge->x += edge->xdir; 59 | // edge->e -= edge->adj_down; 60 | // } 61 | 62 | /// 16.16 fixed point representation. 63 | type Dot16 = i32; 64 | /// 30.2 fixed point representation. 65 | type Dot2 = i32; 66 | /// 26.6 fixed point representation. 67 | type Dot6 = i32; 68 | /// A "sliding fixed point" representation 16.16 >> shift. 69 | type ShiftedDot16 = i32; 70 | 71 | 72 | fn div_fixed16_fixed16(a: Dot16, b: Dot16) -> Dot16 { 73 | (((a as i64) << 16) / (b as i64)) as i32 74 | } 75 | 76 | #[inline] 77 | fn dot2_to_dot16(val: Dot2) -> Dot16 { 78 | val << (16 - SAMPLE_SHIFT) 79 | } 80 | 81 | #[inline] 82 | fn dot16_to_dot2(val: Dot2) -> Dot16 { 83 | val >> (16 - SAMPLE_SHIFT) 84 | } 85 | 86 | #[inline] 87 | fn dot2_to_int(val: Dot2) -> i32 { 88 | val >> SAMPLE_SHIFT 89 | } 90 | 91 | #[inline] 92 | fn int_to_dot2(val: i32) -> Dot2 { 93 | val << SAMPLE_SHIFT 94 | } 95 | 96 | #[inline] 97 | fn f32_to_dot2(val: f32) -> Dot2 { 98 | (val * SAMPLE_SIZE) as i32 99 | } 100 | 101 | #[inline] 102 | fn dot2_to_dot6(val: Dot2) -> Dot6 { 103 | val << 4 104 | } 105 | 106 | // it is possible to fit this into 64 bytes on x86-64 107 | // with the following layout: 108 | // 109 | // 4 x2,y2 110 | // 8 next 111 | // 6*4 slope_x,fullx,next_x,next_y, old_x,old_y 112 | // 4*4 dx,ddx,dy,ddy 113 | // 2 cury 114 | // 1 count 115 | // 1 shift 116 | // 117 | // some example counts 5704 curves, 1720 lines 7422 edges 118 | pub struct ActiveEdge { 119 | x2: Dot2, 120 | y2: Dot2, 121 | next: Option>, 122 | slope_x: Dot16, 123 | fullx: Dot16, 124 | next_x: Dot16, 125 | next_y: Dot16, 126 | 127 | dx: Dot16, 128 | ddx: ShiftedDot16, 129 | dy: Dot16, 130 | ddy: ShiftedDot16, 131 | 132 | old_x: Dot16, 133 | old_y: Dot16, 134 | 135 | // Shift is used to "scale" how fast we move along the quadratic curve. 136 | // The key is that we scale dx and dy by the same amount so it doesn't need to have a physical unit 137 | // as long as it doesn't overshoot. 138 | // shift isdiv_fixed16_fixed16 probably also needed to balance number of bits that we need and make sure we don't 139 | // overflow the 32 bits we have. 140 | // It looks like some of quantities are stored in a "sliding fixed point" representation where the 141 | // point depends on the shift. 142 | shift: i32, 143 | // we need to use count so that we make sure that we always line the last point up 144 | // exactly. i.e. we don't have a great way to know when we're at the end implicitly. 145 | count: i32, 146 | winding: i8, // the direction of the edge 147 | } 148 | 149 | impl ActiveEdge { 150 | fn new() -> ActiveEdge { 151 | ActiveEdge { 152 | x2: 0, 153 | y2: 0, 154 | next: None, 155 | slope_x: 0, 156 | fullx: 0, 157 | next_x: 0, 158 | next_y: 0, 159 | dx: 0, 160 | ddx: 0, 161 | dy: 0, 162 | ddy: 0, 163 | old_x: 0, 164 | old_y: 0, 165 | shift: 0, 166 | count: 0, 167 | winding: 0, 168 | } 169 | } 170 | 171 | // we want this to inline into step_edges() to 172 | // avoid the call overhead 173 | fn step(&mut self, cury: Dot2) { 174 | // if we have a shift that means we have a curve 175 | if self.shift != 0 { 176 | if cury >= dot16_to_dot2(self.next_y) { 177 | self.old_y = self.next_y; 178 | self.old_x = self.next_x; 179 | self.fullx = self.next_x; 180 | // increment until we have a next_y that's greater 181 | while self.count > 0 && cury >= dot16_to_dot2(self.next_y) { 182 | self.next_x += self.dx >> self.shift; 183 | self.dx += self.ddx; 184 | self.next_y += self.dy >> self.shift; 185 | self.dy += self.ddy; 186 | self.count -= 1; 187 | } 188 | if self.count == 0 { 189 | // for the last line sgement we can 190 | // just set next_y,x to the end point 191 | self.next_y = dot2_to_dot16(self.y2); 192 | self.next_x = dot2_to_dot16(self.x2); 193 | } 194 | // update slope if we're going to be using it 195 | // we want to avoid dividing by 0 which can happen if we exited the loop above early 196 | if (cury + 1) < self.y2 { 197 | self.slope_x = div_fixed16_fixed16(self.next_x - self.old_x, self.next_y - self.old_y) >> 2; 198 | } 199 | } 200 | self.fullx += self.slope_x; 201 | } else { 202 | // XXX: look into bresenham to control error here 203 | self.fullx += self.slope_x; 204 | } 205 | } 206 | } 207 | 208 | 209 | 210 | pub struct Rasterizer { 211 | edge_starts: Vec>>, 212 | width: i32, 213 | height: i32, 214 | cur_y: Dot2, 215 | 216 | // we use this rect to track the bounds of the added edges 217 | bounds_top: i32, 218 | bounds_bottom: i32, 219 | bounds_left: i32, 220 | bounds_right: i32, 221 | 222 | active_edges: Option>, 223 | 224 | edge_arena: Arena, 225 | } 226 | 227 | impl Rasterizer { 228 | pub fn new(width: i32, height: i32) -> Rasterizer { 229 | let mut edge_starts = Vec::new(); 230 | for _ in 0..int_to_dot2(height) { 231 | edge_starts.push(None); 232 | } 233 | Rasterizer { 234 | width: int_to_dot2(width), 235 | height: int_to_dot2(height), 236 | bounds_right: 0, 237 | bounds_left: width, 238 | bounds_top: height, 239 | bounds_bottom: 0, 240 | cur_y: 0, 241 | edge_starts, 242 | edge_arena: Arena::new(), 243 | active_edges: None, 244 | } 245 | } 246 | } 247 | 248 | // A cheap version of the "Alpha max plus beta min" algorithm (⍺=1, β=0.5) 249 | fn cheap_distance(mut dx: Dot6, mut dy: Dot6) -> Dot6 { 250 | dx = dx.abs(); 251 | dy = dy.abs(); 252 | // return max + min/2 253 | if dx > dy { 254 | dx + (dy >> 1) 255 | } else { 256 | dy + (dx >> 1) 257 | } 258 | } 259 | 260 | fn diff_to_shift(dx: Dot6, dy: Dot6) -> i32 { 261 | // cheap calc of distance from center of p0-p2 to the center of the curve 262 | let mut dist = cheap_distance(dx, dy); 263 | 264 | // shift down dist (it is currently in dot6) 265 | // down by 5 should give us 1/2 pixel accuracy (assuming our dist is accurate...) 266 | // this is chosen by heuristic: make it as big as possible (to minimize segments) 267 | // ... but small enough so that our curves still look smooth 268 | dist = (dist + (1 << 4)) >> 5; 269 | 270 | // each subdivision (shift value) cuts this dist (error) by 1/4 271 | (32 - ((dist as u32).leading_zeros()) as i32) >> 1 272 | } 273 | 274 | // this metric is taken from skia 275 | fn compute_curve_steps(e: &Edge) -> i32 { 276 | let dx = e.control_x * 2 - e.x1 - e.x2; 277 | let dy = e.control_y * 2 - e.y1 - e.y2; 278 | let shift = diff_to_shift(dot2_to_dot6(dx), dot2_to_dot6(dy)); 279 | assert!(shift >= 0); 280 | 281 | shift 282 | } 283 | 284 | const SAMPLE_SIZE: f32 = (1 << SAMPLE_SHIFT) as f32; 285 | pub const SAMPLE_SHIFT: i32 = 2; 286 | 287 | /* We store 1<= self.height { 339 | return; 340 | } 341 | 342 | // drop horizontal edges 343 | if cury >= e.y2 { 344 | return; 345 | } 346 | 347 | self.bounds_top = self.bounds_top.min(dot2_to_int(edge.y1)); 348 | self.bounds_bottom = self.bounds_bottom.max(dot2_to_int(edge.y2 + 3)); 349 | 350 | self.bounds_left = self.bounds_left.min(dot2_to_int(edge.x1)); 351 | self.bounds_left = self.bounds_left.min(dot2_to_int(edge.x2)); 352 | 353 | self.bounds_right = self.bounds_right.max(dot2_to_int(edge.x1 + 3)); 354 | self.bounds_right = self.bounds_right.max(dot2_to_int(edge.x2 + 3)); 355 | 356 | if curve { 357 | self.bounds_left = self.bounds_left.min(dot2_to_int(edge.control_x)); 358 | self.bounds_right = self.bounds_right.max(dot2_to_int(edge.control_x + 3)); 359 | 360 | // Based on Skia 361 | // we'll iterate t from 0..1 (0-256) 362 | // range of A is 4 times coordinate-range 363 | // we can get more accuracy here by using the input points instead of the rounded versions 364 | // A is derived from `dot2_to_dot16(2 * (from - 2 * ctrl + to))`, it is the second derivative of the 365 | // quadratic bézier. 366 | let mut A = (edge.x1 - edge.control_x - edge.control_x + edge.x2) << (15 - SAMPLE_SHIFT); 367 | let mut B = edge.control_x - edge.x1; // The derivative at the start of the curve is 2 * (ctrl - from). 368 | //let mut C = edge.x1; 369 | let mut shift = compute_curve_steps(&edge); 370 | 371 | if shift == 0 { 372 | shift = 1; 373 | } else if shift > MAX_COEFF_SHIFT { 374 | shift = MAX_COEFF_SHIFT; 375 | } 376 | e.shift = shift; 377 | e.count = 1 << shift; 378 | e.dx = 2 * (A >> shift) + 2 * B * (1 << (16 - SAMPLE_SHIFT)); 379 | e.ddx = 2 * (A >> (shift - 1)); 380 | 381 | A = (edge.y1 - edge.control_y - edge.control_y + edge.y2) << (15 - SAMPLE_SHIFT); 382 | B = edge.control_y - edge.y1; 383 | //C = edge.y1; 384 | e.dy = 2 * (A >> shift) + 2 * B * (1 << (16 - SAMPLE_SHIFT)); 385 | e.ddy = 2 * (A >> (shift - 1)); 386 | 387 | // compute the first next_x,y 388 | e.count -= 1; 389 | e.next_x = (e.fullx) + (e.dx >> e.shift); 390 | e.next_y = (cury * (1 << (16 - SAMPLE_SHIFT))) + (e.dy >> e.shift); 391 | e.dx += e.ddx; 392 | e.dy += e.ddy; 393 | 394 | // skia does this part in UpdateQuad. unfortunately we duplicate it 395 | while e.count > 0 && cury >= dot16_to_dot2(e.next_y) { 396 | e.next_x += e.dx >> shift; 397 | e.dx += e.ddx; 398 | e.next_y += e.dy >> shift; 399 | e.dy += e.ddy; 400 | e.count -= 1; 401 | } 402 | if e.count == 0 { 403 | e.next_y = dot2_to_dot16(edge.y2); 404 | e.next_x = dot2_to_dot16(edge.x2); 405 | } 406 | e.slope_x = (e.next_x - (e.fullx)) / dot16_to_dot2(e.next_y - dot2_to_dot16(cury)); 407 | } else { 408 | e.shift = 0; 409 | e.slope_x = (edge.x2 - edge.x1) * (1 << (16 - SAMPLE_SHIFT)) / (edge.y2 - edge.y1); 410 | } 411 | 412 | if cury < 0 { 413 | // XXX: we could compute an intersection with the top and bottom so we don't need to step them into view 414 | // for curves we can just step them into place. 415 | while cury < 0 { 416 | e.step(cury); 417 | cury += 1; 418 | } 419 | 420 | // cury was adjusted so check again for horizontal edges 421 | if cury >= e.y2 { 422 | return; 423 | } 424 | } 425 | 426 | // add to the beginning of the edge start list 427 | // if edges are added from left to right 428 | // they'll be in this list from right to left 429 | // this works out later during insertion 430 | e.next = self.edge_starts[cury as usize]; 431 | self.edge_starts[cury as usize] = Some(NonNull::from(e)); 432 | } 433 | 434 | fn step_edges(&mut self) { 435 | let mut prev_ptr = &mut self.active_edges as *mut _; 436 | let mut edge = self.active_edges; 437 | let cury = self.cur_y; // avoid any aliasing problems 438 | while let Some(mut e_ptr) = edge { 439 | let e = unsafe { e_ptr.as_mut() }; 440 | e.step(cury); 441 | // avoid aliasing between edge->next and prev_ptr so that we can reuse next 442 | let next = e.next; 443 | // remove any finished edges 444 | if (cury + 1) >= e.y2 { 445 | // remove from active list 446 | unsafe { *prev_ptr = next }; 447 | } else { 448 | prev_ptr = &mut e.next; 449 | } 450 | edge = next; 451 | } 452 | } 453 | /* 454 | int comparisons; 455 | static inline void dump_edges(ActiveEdge *e) 456 | { 457 | while (e) { 458 | printf("%d ", e.fullx); 459 | e = e.next; 460 | } 461 | printf("\n"); 462 | } 463 | */ 464 | // Insertion sort the new edges into the active list 465 | // The new edges could be showing up at any x coordinate 466 | // but existing active edges will be sorted. 467 | // 468 | // Merge in the new edges. Since both lists are sorted we can do 469 | // this in a single pass. 470 | // Note: we could do just O(1) append the list of new active edges 471 | // to the existing active edge list, but then we'd have to sort 472 | // the entire resulting list 473 | fn insert_starting_edges(&mut self) { 474 | let mut new_edges: Option> = None; 475 | let mut edge = self.edge_starts[self.cur_y as usize]; 476 | // insertion sort all of the new edges 477 | while let Some(mut e_ptr) = edge { 478 | let e = unsafe { e_ptr.as_mut() }; 479 | let mut prev_ptr = &mut new_edges as *mut _; 480 | let mut new = new_edges; 481 | while let Some(mut new_ptr) = new { 482 | let a = unsafe { new_ptr.as_mut() }; 483 | if e.fullx <= a.fullx { 484 | break; 485 | } 486 | // comparisons++; 487 | prev_ptr = &mut a.next; 488 | new = a.next; 489 | } 490 | edge = e.next; 491 | e.next = new; 492 | unsafe { *prev_ptr = Some(e_ptr) }; 493 | } 494 | 495 | // merge the sorted new_edges into active_edges 496 | let mut prev_ptr = &mut self.active_edges as *mut _; 497 | let mut active = self.active_edges; 498 | let mut edge = new_edges; 499 | while let Some(mut e_ptr) = edge { 500 | let e = unsafe { e_ptr.as_mut() }; 501 | while let Some(mut a_ptr) = active { 502 | let a = unsafe { a_ptr.as_mut() }; 503 | if e.fullx <= a.fullx { 504 | break; 505 | } 506 | 507 | // comparisons++; 508 | prev_ptr = &mut a.next; 509 | active = a.next; 510 | } 511 | edge = e.next; 512 | e.next = active; 513 | let next_prev_ptr = &mut e.next as *mut _; 514 | unsafe { *prev_ptr = Some(e_ptr) }; 515 | prev_ptr = next_prev_ptr; 516 | } 517 | } 518 | 519 | // Skia does stepping and scanning of edges in a single 520 | // pass over the edge list. 521 | fn scan_edges(&mut self, blitter: &mut dyn RasterBlitter, winding_mode: Winding) { 522 | let mut edge = self.active_edges; 523 | let mut winding = 0; 524 | 525 | // handle edges that begin to the left of the bitmap 526 | while let Some(mut e_ptr) = edge { 527 | let e = unsafe { e_ptr.as_mut() }; 528 | if e.fullx >= 0 { 529 | break; 530 | } 531 | winding += e.winding as i32; 532 | edge = e.next; 533 | } 534 | 535 | let mut prevx = 0; 536 | while let Some(mut e_ptr) = edge { 537 | let e = unsafe { e_ptr.as_mut() }; 538 | 539 | let inside = match winding_mode { 540 | Winding::EvenOdd => winding & 1 != 0, 541 | Winding::NonZero => winding != 0, 542 | }; 543 | 544 | if inside { 545 | blitter.blit_span( 546 | self.cur_y, 547 | dot16_to_dot2(prevx + (1 << (15 - SAMPLE_SHIFT))), 548 | dot16_to_dot2(e.fullx + (1 << (15 - SAMPLE_SHIFT))), 549 | ); 550 | } 551 | 552 | if dot16_to_dot2(e.fullx) >= self.width { 553 | break; 554 | } 555 | winding += e.winding as i32; 556 | prevx = e.fullx; 557 | edge = e.next; 558 | } 559 | 560 | // we don't need to worry about any edges beyond width 561 | } 562 | 563 | // You may have heard that one should never use a bubble sort. 564 | // However in our situation a bubble sort is actually a good choice. 565 | // The input list will be mostly sorted except for a couple of lines 566 | // that have need to be swapped. Further it is common that our edges are 567 | // already sorted and bubble sort lets us avoid doing any memory writes. 568 | 569 | // Some statistics from using a bubble sort on an 570 | // example scene. You can see that bubble sort does 571 | // noticeably better than O (n lg n). 572 | // summary(edges*bubble_sort_iterations) 573 | // Min. 1st Qu. Median Mean 3rd Qu. Max. 574 | // 0.0 9.0 69.0 131.5 206.0 1278.0 575 | // summary(edges*log2(edges)) 576 | // Min. 1st Qu. Median Mean 3rd Qu. Max. NA's 577 | // 0.00 28.53 347.10 427.60 787.20 1286.00 2.00 578 | fn sort_edges(&mut self) { 579 | if self.active_edges.is_none() { 580 | return; 581 | } 582 | 583 | let mut swapped; 584 | loop { 585 | swapped = false; 586 | let mut edge = self.active_edges.unwrap(); 587 | let mut next_edge = unsafe { edge.as_mut() }.next; 588 | let mut prev = &mut self.active_edges as *mut _; 589 | while let Some(mut next_ptr) = next_edge { 590 | let next = unsafe { next_ptr.as_mut() }; 591 | if unsafe { edge.as_mut() }.fullx > next.fullx { 592 | // swap edge and next 593 | unsafe { edge.as_mut() }.next = next.next; 594 | next.next = Some(edge); 595 | unsafe { (*prev) = Some(next_ptr) }; 596 | swapped = true; 597 | } 598 | prev = (&mut unsafe { edge.as_mut() }.next) as *mut _; 599 | edge = next_ptr; 600 | next_edge = unsafe { edge.as_mut() }.next; 601 | } 602 | if !swapped { 603 | break; 604 | } 605 | } 606 | } 607 | 608 | pub fn rasterize(&mut self, blitter: &mut dyn RasterBlitter, winding_mode: Winding) { 609 | let start = int_to_dot2(self.bounds_top).max(0); 610 | let end = int_to_dot2(self.bounds_bottom).min(self.height); 611 | 612 | self.cur_y = start; 613 | while self.cur_y < end { 614 | // we do 4x4 super-sampling so we need 615 | // to scan 4 times before painting a line of pixels 616 | for _ in 0..4 { 617 | // insert the new edges into the sorted list 618 | self.insert_starting_edges(); 619 | // scan over the edge list producing a list of spans 620 | self.scan_edges(blitter, winding_mode); 621 | // step all of the edges to the next scanline 622 | // dropping the ones that end 623 | self.step_edges(); 624 | // sort the remaining edges 625 | self.sort_edges(); 626 | self.cur_y += 1; 627 | } 628 | } 629 | } 630 | 631 | pub fn get_bounds(&self) -> IntRect { 632 | intrect(self.bounds_left.max(0), 633 | self.bounds_top.max(0), 634 | self.bounds_right.min(dot2_to_int(self.width)), 635 | self.bounds_bottom.min(dot2_to_int(self.height))) 636 | } 637 | 638 | pub fn reset(&mut self) { 639 | if self.bounds_bottom < self.bounds_top { 640 | debug_assert_eq!(self.active_edges, None); 641 | for e in &mut self.edge_starts { 642 | debug_assert_eq!(*e, None); 643 | } 644 | debug_assert_eq!(self.bounds_bottom, 0); 645 | debug_assert_eq!(self.bounds_right, 0); 646 | debug_assert_eq!(self.bounds_top, dot2_to_int(self.height)); 647 | debug_assert_eq!(self.bounds_left, dot2_to_int(self.width)); 648 | // Currently we allocate an edge in the arena even if we don't 649 | // end up putting it in the edge_starts list. Avoiding that 650 | // would let us avoiding having to reinitialize the arena 651 | self.edge_arena = Arena::new(); 652 | return; 653 | } 654 | let start = int_to_dot2(self.bounds_top).max(0) as usize; 655 | let end = int_to_dot2(self.bounds_bottom).min(self.height) as usize; 656 | self.active_edges = None; 657 | for e in &mut self.edge_starts[start..end] { 658 | *e = None; 659 | } 660 | self.edge_arena = Arena::new(); 661 | self.bounds_bottom = 0; 662 | self.bounds_right = 0; 663 | self.bounds_top = dot2_to_int(self.height); 664 | self.bounds_left = dot2_to_int(self.width); 665 | } 666 | } 667 | -------------------------------------------------------------------------------- /src/blitter.rs: -------------------------------------------------------------------------------- 1 | use sw_composite::*; 2 | 3 | use crate::{IntPoint, Point, Transform}; 4 | use crate::draw_target::{ExtendMode, Source, FilterMode}; 5 | 6 | use euclid::vec2; 7 | use std::marker::PhantomData; 8 | 9 | pub trait Blitter { 10 | fn blit_span(&mut self, y: i32, x1: i32, x2: i32, mask: &[u8]); 11 | } 12 | 13 | pub trait RasterBlitter { 14 | fn blit_span(&mut self, y: i32, x1: i32, x2: i32); 15 | } 16 | 17 | pub struct MaskSuperBlitter { 18 | pub x: i32, 19 | pub y: i32, 20 | width: i32, 21 | pub buf: Vec, 22 | } 23 | 24 | const SHIFT: i32 = crate::rasterizer::SAMPLE_SHIFT; 25 | const SCALE: i32 = 1 << SHIFT; 26 | const MASK: i32 = SCALE - 1; 27 | const SUPER_MASK: i32 = (1 << SHIFT) - 1; 28 | 29 | fn coverage_to_partial_alpha(mut aa: i32) -> u8 { 30 | aa <<= 8 - 2 * SHIFT; 31 | return aa as u8; 32 | } 33 | 34 | impl MaskSuperBlitter { 35 | pub fn new(x: i32, y: i32, width: i32, height: i32) -> MaskSuperBlitter { 36 | MaskSuperBlitter { 37 | x: x * SCALE, y: y * SCALE, 38 | width, 39 | // we can end up writing one byte past the end of the buffer so allocate that 40 | // padding to avoid needing to do an extra check 41 | buf: vec![0; (width * height) as usize + 1], 42 | } 43 | } 44 | } 45 | 46 | // Perform this tricky subtract, to avoid overflowing to 256. Our caller should 47 | // only ever call us with at most enough to hit 256 (never larger), so it is 48 | // enough to just subtract the high-bit. Actually clamping with a branch would 49 | // be slower (e.g. if (tmp > 255) tmp = 255;) 50 | fn saturated_add(a: u8, b: u8) -> u8 { 51 | let tmp = a as u32 + b as u32; 52 | let result = tmp - (tmp >> 8); 53 | result as u8 54 | } 55 | 56 | impl RasterBlitter for MaskSuperBlitter { 57 | fn blit_span(&mut self, mut y: i32, mut x1: i32, mut x2: i32) { 58 | y -= self.y; 59 | x1 -= self.x; 60 | x2 -= self.x; 61 | x2 = x2.min(self.width * SCALE); 62 | let max: u8 = ((1 << (8 - SHIFT)) - (((y & MASK) + 1) >> SHIFT)) as u8; 63 | let start = (y / 4 * self.width) as usize; 64 | 65 | let mut fb = x1 & SUPER_MASK; 66 | let fe = x2 & SUPER_MASK; 67 | let b = &mut self.buf[start + (x1 >> SHIFT) as usize..start + (x2 >> SHIFT) as usize + 1]; 68 | let len = b.len(); 69 | 70 | // invert the alpha on the left side 71 | if len == 0 { 72 | } else if len == 1 { 73 | b[0] = saturated_add(b[0], coverage_to_partial_alpha(fe - fb)); 74 | } else { 75 | fb = (1 << SHIFT) - fb; 76 | b[0] = saturated_add(b[0], coverage_to_partial_alpha(fb)); 77 | 78 | // Rust seems to emit bounds checks here when it should be able to avoid them 79 | for i in &mut b[1..len-1] { 80 | *i += max; 81 | } 82 | b[len-1] = saturated_add(b[len-1], coverage_to_partial_alpha(fe)); 83 | } 84 | } 85 | } 86 | 87 | pub struct MaskBlitter { 88 | pub x: i32, 89 | pub y: i32, 90 | width: i32, 91 | pub buf: Vec, 92 | } 93 | 94 | impl MaskBlitter { 95 | pub fn new(x: i32, y: i32, width: i32, height: i32) -> MaskBlitter { 96 | MaskBlitter { 97 | x: x * SCALE, 98 | y: y * SCALE, 99 | width, 100 | // we can end up writing one byte past the end of the buffer so allocate that 101 | // padding to avoid needing to do an extra check 102 | buf: vec![0; (width * height) as usize + 1], 103 | } 104 | } 105 | } 106 | 107 | impl RasterBlitter for MaskBlitter { 108 | fn blit_span(&mut self, mut y: i32, mut x1: i32, mut x2: i32) { 109 | y -= self.y; 110 | x1 -= self.x; 111 | x2 -= self.x; 112 | if y % SCALE != 0 { 113 | return; 114 | } 115 | 116 | x2 = x2.min(self.width * SCALE); 117 | 118 | x1 >>= SHIFT; 119 | x2 >>= SHIFT; 120 | 121 | for i in x1..x2 { 122 | self.buf[(y / 4 * self.width + i) as usize] = 0xff; 123 | } 124 | } 125 | } 126 | 127 | pub trait Shader { 128 | fn shade_span(&self, x: i32, y: i32, dest: &mut [u32], count: usize); 129 | } 130 | 131 | pub struct SolidShader { 132 | pub color: u32, 133 | } 134 | 135 | impl Shader for SolidShader { 136 | fn shade_span(&self, _x: i32, _y: i32, dest: &mut [u32], count: usize) { 137 | for i in 0..count { 138 | dest[i] = self.color; 139 | } 140 | } 141 | } 142 | 143 | fn transform_to_fixed(transform: &Transform) -> MatrixFixedPoint { 144 | MatrixFixedPoint { 145 | xx: float_to_fixed(transform.m11), 146 | xy: float_to_fixed(transform.m21), 147 | yx: float_to_fixed(transform.m12), 148 | yy: float_to_fixed(transform.m22), 149 | x0: float_to_fixed(transform.m31), 150 | y0: float_to_fixed(transform.m32), 151 | } 152 | } 153 | 154 | pub struct TransformedImageShader<'a, 'b, Fetch: PixelFetch> { 155 | image: &'a Image<'b>, 156 | xfm: MatrixFixedPoint, 157 | fetch: PhantomData, 158 | } 159 | 160 | impl<'a, 'b, Fetch: PixelFetch> TransformedImageShader<'a, 'b, Fetch> { 161 | pub fn new(image: &'a Image<'b>, transform: &Transform) -> TransformedImageShader<'a, 'b, Fetch> { 162 | TransformedImageShader { 163 | image, 164 | xfm: transform_to_fixed(&transform.pre_translate(vec2(0.5, 0.5)).then_translate(vec2(-0.5, -0.5))), 165 | fetch: PhantomData, 166 | } 167 | } 168 | } 169 | 170 | impl<'a, 'b, Fetch: PixelFetch> Shader for TransformedImageShader<'a, 'b, Fetch> { 171 | fn shade_span(&self, mut x: i32, y: i32, dest: &mut [u32], count: usize) { 172 | for i in 0..count { 173 | let p = self.xfm.transform(x as u16, y as u16); 174 | dest[i] = fetch_bilinear::(self.image, p.x, p.y); 175 | x += 1; 176 | } 177 | } 178 | } 179 | 180 | pub struct TransformedImageAlphaShader<'a, 'b, Fetch: PixelFetch> { 181 | image: &'a Image<'b>, 182 | xfm: MatrixFixedPoint, 183 | alpha: u32, 184 | fetch: PhantomData, 185 | } 186 | 187 | impl<'a, 'b, Fetch: PixelFetch> TransformedImageAlphaShader<'a, 'b, Fetch> { 188 | pub fn new(image: &'a Image<'b>, transform: &Transform, alpha: u32) -> TransformedImageAlphaShader<'a, 'b, Fetch> { 189 | TransformedImageAlphaShader { 190 | image, 191 | xfm: transform_to_fixed(&transform.pre_translate(vec2(0.5, 0.5)).then_translate(vec2(-0.5, -0.5))), 192 | alpha: alpha_to_alpha256(alpha), 193 | fetch: PhantomData, 194 | } 195 | } 196 | } 197 | 198 | impl<'a, 'b, Fetch: PixelFetch> Shader for TransformedImageAlphaShader<'a, 'b, Fetch> { 199 | fn shade_span(&self, mut x: i32, y: i32, dest: &mut [u32], count: usize) { 200 | for i in 0..count { 201 | let p = self.xfm.transform(x as u16, y as u16); 202 | dest[i] = fetch_bilinear_alpha::(self.image, p.x, p.y, self.alpha); 203 | x += 1; 204 | } 205 | } 206 | } 207 | 208 | pub struct TransformedNearestImageShader<'a, 'b, Fetch: PixelFetch> { 209 | image: &'a Image<'b>, 210 | xfm: MatrixFixedPoint, 211 | fetch: PhantomData, 212 | } 213 | 214 | impl<'a, 'b, Fetch: PixelFetch> TransformedNearestImageShader<'a, 'b, Fetch> { 215 | pub fn new(image: &'a Image<'b>, transform: &Transform) -> TransformedNearestImageShader<'a, 'b, Fetch> { 216 | TransformedNearestImageShader { 217 | image, 218 | xfm: transform_to_fixed(&transform.pre_translate(vec2(0.5, 0.5)).then_translate(vec2(-0.5, -0.5))), 219 | fetch: PhantomData, 220 | } 221 | } 222 | } 223 | 224 | impl<'a, 'b, Fetch: PixelFetch> Shader for TransformedNearestImageShader<'a, 'b, Fetch> { 225 | fn shade_span(&self, mut x: i32, y: i32, dest: &mut [u32], count: usize) { 226 | for i in 0..count { 227 | let p = self.xfm.transform(x as u16, y as u16); 228 | dest[i] = fetch_nearest::(self.image, p.x, p.y); 229 | x += 1; 230 | } 231 | } 232 | } 233 | 234 | pub struct TransformedNearestImageAlphaShader<'a, 'b, Fetch: PixelFetch> { 235 | image: &'a Image<'b>, 236 | xfm: MatrixFixedPoint, 237 | alpha: u32, 238 | fetch: PhantomData, 239 | } 240 | 241 | impl<'a, 'b, Fetch: PixelFetch> TransformedNearestImageAlphaShader<'a, 'b, Fetch> { 242 | pub fn new(image: &'a Image<'b>, transform: &Transform, alpha: u32) -> TransformedNearestImageAlphaShader<'a, 'b, Fetch> { 243 | TransformedNearestImageAlphaShader { 244 | image, 245 | xfm: transform_to_fixed(&transform.pre_translate(vec2(0.5, 0.5)).then_translate(vec2(-0.5, -0.5))), 246 | alpha: alpha_to_alpha256(alpha), 247 | fetch: PhantomData, 248 | } 249 | } 250 | } 251 | 252 | impl<'a, 'b, Fetch: PixelFetch> Shader for TransformedNearestImageAlphaShader<'a, 'b, Fetch> { 253 | fn shade_span(&self, mut x: i32, y: i32, dest: &mut [u32], count: usize) { 254 | for i in 0..count { 255 | let p = self.xfm.transform(x as u16, y as u16); 256 | dest[i] = fetch_nearest_alpha::(self.image, p.x, p.y, self.alpha); 257 | x += 1; 258 | } 259 | } 260 | } 261 | 262 | pub struct ImagePadAlphaShader<'a, 'b> { 263 | image: &'a Image<'b>, 264 | offset_x: i32, 265 | offset_y: i32, 266 | alpha: u32, 267 | } 268 | 269 | impl<'a, 'b> ImagePadAlphaShader<'a, 'b> { 270 | pub fn new(image: &'a Image<'b>, x: i32, y: i32, alpha: u32) -> ImagePadAlphaShader<'a, 'b> { 271 | ImagePadAlphaShader { 272 | image, 273 | offset_x: x, 274 | offset_y: y, 275 | alpha: alpha_to_alpha256(alpha), 276 | } 277 | } 278 | } 279 | 280 | impl<'a, 'b> Shader for ImagePadAlphaShader<'a, 'b> { 281 | fn shade_span(&self, mut x: i32, mut y: i32, dest: &mut [u32], mut count: usize) { 282 | x += self.offset_x; 283 | y += self.offset_y; 284 | let mut dest_x = 0; 285 | 286 | if y < 0 { 287 | y = 0; 288 | } else if y >= self.image.height { 289 | y = self.image.height - 1; 290 | } 291 | 292 | while x < 0 && count > 0 { 293 | dest[dest_x] = alpha_mul(self.image.data[(self.image.width * y) as usize], self.alpha); 294 | x += 1; 295 | dest_x += 1; 296 | count -= 1; 297 | } 298 | if count > 0 && x < self.image.width { 299 | let len = count.min((self.image.width - x) as usize); 300 | let d = &mut dest[dest_x..dest_x + len]; 301 | let src_start = (self.image.width * y + x) as usize; 302 | let s = &self.image.data[src_start..src_start + len]; 303 | 304 | for (d, s) in d.iter_mut().zip(s) { 305 | *d = alpha_mul(*s, self.alpha); 306 | } 307 | 308 | dest_x += len; 309 | count -= len; 310 | } 311 | while count > 0 { 312 | dest[dest_x] = alpha_mul(self.image.data[(self.image.width * y + self.image.width - 1) as usize], self.alpha); 313 | dest_x += 1; 314 | count -= 1; 315 | } 316 | } 317 | } 318 | 319 | pub struct ImageRepeatAlphaShader<'a, 'b> { 320 | image: &'a Image<'b>, 321 | offset_x: i32, 322 | offset_y: i32, 323 | alpha: u32, 324 | } 325 | 326 | impl<'a, 'b> ImageRepeatAlphaShader<'a, 'b> { 327 | pub fn new(image: &'a Image<'b>, x: i32, y: i32, alpha: u32) -> ImageRepeatAlphaShader<'a, 'b> { 328 | ImageRepeatAlphaShader { 329 | image, 330 | offset_x: x, 331 | offset_y: y, 332 | alpha: alpha_to_alpha256(alpha), 333 | } 334 | } 335 | } 336 | 337 | impl<'a, 'b> Shader for ImageRepeatAlphaShader<'a, 'b> { 338 | fn shade_span(&self, mut x: i32, mut y: i32, dest: &mut [u32], mut count: usize) { 339 | x += self.offset_x; 340 | y += self.offset_y; 341 | let mut dest_x = 0; 342 | 343 | let y = y.rem_euclid(self.image.height); 344 | let mut x = x.rem_euclid(self.image.width); 345 | 346 | while count > 0 { 347 | let len = count.min((self.image.width - x) as usize); 348 | let d = &mut dest[dest_x..dest_x + len]; 349 | let src_start = (self.image.width * y + x) as usize; 350 | let s = &self.image.data[src_start..src_start + len]; 351 | 352 | for (d, s) in d.iter_mut().zip(s) { 353 | *d = alpha_mul(*s, self.alpha); 354 | } 355 | 356 | dest_x += len; 357 | count -= len; 358 | x = 0; 359 | } 360 | } 361 | } 362 | 363 | pub struct RadialGradientShader { 364 | gradient: Box, 365 | spread: Spread, 366 | } 367 | 368 | impl RadialGradientShader { 369 | pub fn new(gradient: &Gradient, transform: &Transform, spread: Spread, alpha: u32) -> RadialGradientShader { 370 | RadialGradientShader { 371 | gradient: gradient.make_source(&transform_to_fixed( 372 | &transform.pre_translate(vec2(0.5, 0.5))), 373 | alpha 374 | ), 375 | spread, 376 | } 377 | } 378 | } 379 | 380 | impl Shader for RadialGradientShader { 381 | fn shade_span(&self, mut x: i32, y: i32, dest: &mut [u32], count: usize) { 382 | for i in 0..count { 383 | dest[i] = self.gradient.radial_gradient_eval(x as u16, y as u16, self.spread); 384 | x += 1; 385 | } 386 | } 387 | } 388 | 389 | pub struct TwoCircleRadialGradientShader { 390 | gradient: Box, 391 | spread: Spread, 392 | } 393 | 394 | impl TwoCircleRadialGradientShader { 395 | pub fn new(gradient: &Gradient, 396 | transform: &Transform, 397 | c1: Point, 398 | r1: f32, 399 | c2: Point, 400 | r2: f32, 401 | spread: Spread, 402 | alpha: u32) -> TwoCircleRadialGradientShader { 403 | TwoCircleRadialGradientShader { 404 | gradient: gradient.make_two_circle_source( 405 | c1.x, c1.y, 406 | r1, 407 | c2.x, c2.y, 408 | r2, 409 | &transform_to_fixed(&transform.pre_translate(vec2(0.5, 0.5))), alpha 410 | ), 411 | spread, 412 | } 413 | } 414 | } 415 | 416 | impl Shader for TwoCircleRadialGradientShader { 417 | fn shade_span(&self, mut x: i32, y: i32, dest: &mut [u32], count: usize) { 418 | for i in 0..count { 419 | dest[i] = self.gradient.eval(x as u16, y as u16, self.spread); 420 | x += 1; 421 | } 422 | } 423 | } 424 | 425 | pub struct SweepGradientShader { 426 | gradient: Box, 427 | spread: Spread, 428 | } 429 | 430 | impl SweepGradientShader { 431 | pub fn new(gradient: &Gradient, 432 | transform: &Transform, 433 | start_angle: f32, 434 | end_angle: f32, 435 | spread: Spread, 436 | alpha: u32) -> SweepGradientShader { 437 | SweepGradientShader { 438 | gradient: gradient.make_sweep_source( 439 | start_angle, 440 | end_angle, 441 | &transform_to_fixed(&transform.pre_translate(vec2(0.5, 0.5))), alpha 442 | ), 443 | spread, 444 | } 445 | } 446 | } 447 | 448 | impl Shader for SweepGradientShader { 449 | fn shade_span(&self, mut x: i32, y: i32, dest: &mut [u32], count: usize) { 450 | for i in 0..count { 451 | dest[i] = self.gradient.eval(x as u16, y as u16, self.spread); 452 | x += 1; 453 | } 454 | } 455 | } 456 | 457 | pub struct LinearGradientShader { 458 | gradient: Box, 459 | spread: Spread, 460 | } 461 | 462 | impl LinearGradientShader { 463 | pub fn new(gradient: &Gradient, transform: &Transform, spread: Spread, alpha: u32) -> LinearGradientShader { 464 | LinearGradientShader { 465 | gradient: gradient.make_source(&transform_to_fixed( 466 | &transform.pre_translate(vec2(0.5, 0.5))), 467 | alpha 468 | ), 469 | spread, 470 | } 471 | } 472 | } 473 | 474 | impl Shader for LinearGradientShader { 475 | fn shade_span(&self, mut x: i32, y: i32, dest: &mut [u32], count: usize) { 476 | for i in 0..count { 477 | dest[i] = self.gradient.linear_gradient_eval(x as u16, y as u16, self.spread); 478 | x += 1; 479 | } 480 | } 481 | } 482 | 483 | pub struct ShaderMaskBlitter<'a> { 484 | pub x: i32, 485 | pub y: i32, 486 | pub shader: &'a dyn Shader, 487 | pub tmp: Vec, 488 | pub dest: &'a mut [u32], 489 | pub dest_stride: i32, 490 | } 491 | 492 | impl<'a> Blitter for ShaderMaskBlitter<'a> { 493 | fn blit_span(&mut self, y: i32, x1: i32, x2: i32, mask: &[u8]) { 494 | let dest_row = (y - self.y) * self.dest_stride; 495 | let count = (x2 - x1) as usize; 496 | self.shader.shade_span(x1, y, &mut self.tmp[..], count); 497 | for i in 0..count { 498 | let mask = mask[i] as u32; 499 | if mask != 0 { 500 | self.dest[(dest_row + x1 - self.x) as usize + i] = over_in( 501 | self.tmp[i], 502 | self.dest[(dest_row + x1 - self.x) as usize + i], 503 | mask, 504 | ); 505 | } 506 | } 507 | } 508 | } 509 | 510 | pub struct ShaderClipMaskBlitter<'a> { 511 | pub x: i32, 512 | pub y: i32, 513 | pub shader: &'a dyn Shader, 514 | pub tmp: Vec, 515 | pub dest: &'a mut [u32], 516 | pub dest_stride: i32, 517 | pub clip: &'a [u8], 518 | pub clip_stride: i32, 519 | } 520 | 521 | impl<'a> Blitter for ShaderClipMaskBlitter<'a> { 522 | fn blit_span(&mut self, y: i32, x1: i32, x2: i32, mask: &[u8]) { 523 | let dest_row = (y - self.y) * self.dest_stride; 524 | let clip_row = y * self.clip_stride; 525 | let count = (x2 - x1) as usize; 526 | self.shader.shade_span(x1, y, &mut self.tmp[..], count); 527 | for i in 0..count { 528 | let mask = mask[i] as u32; 529 | let clip = self.clip[(clip_row + x1) as usize + i] as u32; 530 | if mask != 0 && clip != 0 { 531 | self.dest[(dest_row + x1 - self.x) as usize + i] = over_in_in( 532 | self.tmp[i], 533 | self.dest[(dest_row + x1 - self.x) as usize + i], 534 | mask, 535 | clip, 536 | ); 537 | } 538 | } 539 | } 540 | } 541 | 542 | pub struct ShaderClipBlendMaskBlitter<'a> { 543 | pub x: i32, 544 | pub y: i32, 545 | pub shader: &'a dyn Shader, 546 | pub tmp: Vec, 547 | pub dest: &'a mut [u32], 548 | pub dest_stride: i32, 549 | pub clip: &'a [u8], 550 | pub clip_stride: i32, 551 | pub blend_fn: fn (&[u32], &[u8], &[u8], &mut [u32]), 552 | } 553 | 554 | impl<'a> Blitter for ShaderClipBlendMaskBlitter<'a> { 555 | fn blit_span(&mut self, y: i32, x1: i32, x2: i32, mask: &[u8]) { 556 | let dest_row = (y - self.y) * self.dest_stride; 557 | let clip_row = y * self.clip_stride; 558 | let count = (x2 - x1) as usize; 559 | self.shader.shade_span(x1, y, &mut self.tmp[..], count); 560 | (self.blend_fn)(&self.tmp[..], 561 | mask, 562 | &self.clip[(clip_row + x1) as usize..], 563 | &mut self.dest[(dest_row + x1 - self.x) as usize..]) 564 | } 565 | } 566 | 567 | pub struct ShaderBlendMaskBlitter<'a> { 568 | pub x: i32, 569 | pub y: i32, 570 | pub shader: &'a dyn Shader, 571 | pub tmp: Vec, 572 | pub dest: &'a mut [u32], 573 | pub dest_stride: i32, 574 | pub blend_fn: fn (&[u32], &[u8], &mut [u32]), 575 | } 576 | 577 | impl<'a> Blitter for ShaderBlendMaskBlitter<'a> { 578 | fn blit_span(&mut self, y: i32, x1: i32, x2: i32, mask: &[u8]) { 579 | let dest_row = (y - self.y) * self.dest_stride; 580 | let count = (x2 - x1) as usize; 581 | self.shader.shade_span(x1, y, &mut self.tmp[..], count); 582 | (self.blend_fn)(&self.tmp[..], 583 | mask, 584 | &mut self.dest[(dest_row + x1 - self.x) as usize..]) 585 | } 586 | } 587 | 588 | pub struct ShaderBlendBlitter<'a> { 589 | pub x: i32, 590 | pub y: i32, 591 | pub shader: &'a dyn Shader, 592 | pub tmp: Vec, 593 | pub dest: &'a mut [u32], 594 | pub dest_stride: i32, 595 | pub blend_fn: fn (&[u32], &mut [u32]), 596 | } 597 | 598 | impl<'a> Blitter for ShaderBlendBlitter<'a> { 599 | fn blit_span(&mut self, y: i32, x1: i32, x2: i32, _: &[u8]) { 600 | let dest_row = (y - self.y) * self.dest_stride; 601 | let count = (x2 - x1) as usize; 602 | self.shader.shade_span(x1, y, &mut self.tmp[..], count); 603 | (self.blend_fn)(&self.tmp[..], 604 | &mut self.dest[(dest_row + x1 - self.x) as usize..]) 605 | } 606 | } 607 | 608 | 609 | fn is_integer_transform(trans: &Transform) -> Option { 610 | if trans.m11 == 1. && 611 | trans.m12 == 0. && 612 | trans.m21 == 0. && 613 | trans.m22 == 1. { 614 | let x = trans.m31 as i32; 615 | let y = trans.m32 as i32; 616 | if x as f32 == trans.m31 && 617 | y as f32 == trans.m32 { 618 | return Some(IntPoint::new(x, y)) 619 | } 620 | } 621 | None 622 | } 623 | 624 | pub enum ShaderStorage<'a, 'b> { 625 | None, 626 | Solid(SolidShader), 627 | ImagePadAlpha(ImagePadAlphaShader<'a, 'b>), 628 | ImageRepeatAlpha(ImageRepeatAlphaShader<'a, 'b>), 629 | TransformedNearestPadImageAlpha(TransformedNearestImageAlphaShader<'a, 'b, PadFetch>), 630 | TransformedNearestRepeatImageAlpha(TransformedNearestImageAlphaShader<'a, 'b, RepeatFetch>), 631 | TransformedPadImageAlpha(TransformedImageAlphaShader<'a, 'b, PadFetch>), 632 | TransformedRepeatImageAlpha(TransformedImageAlphaShader<'a, 'b, RepeatFetch>), 633 | TransformedPadImage(TransformedImageShader<'a, 'b, PadFetch>), 634 | TransformedRepeatImage(TransformedImageShader<'a, 'b, RepeatFetch>), 635 | TransformedNearestPadImage(TransformedNearestImageShader<'a, 'b, PadFetch>), 636 | TransformedNearestRepeatImage(TransformedNearestImageShader<'a, 'b, RepeatFetch>), 637 | RadialGradient(RadialGradientShader), 638 | TwoCircleRadialGradient(TwoCircleRadialGradientShader), 639 | LinearGradient(LinearGradientShader), 640 | SweepGradient(SweepGradientShader), 641 | } 642 | 643 | // The idea here is to store a shader in shader_storage and then return 644 | // a reference to it. The goal is to avoid a heap allocation but the end 645 | // result is pretty ugly. 646 | pub fn choose_shader<'a, 'b, 'c>(ti: &Transform, src: &'b Source<'c>, alpha: f32, shader_storage: &'a mut ShaderStorage<'b, 'c>) -> &'a dyn Shader { 647 | // XXX: clamp alpha 648 | let alpha = (alpha * 255. + 0.5) as u32; 649 | 650 | *shader_storage = match src { 651 | Source::Solid(c) => { 652 | let color = alpha_mul(c.to_u32(), alpha_to_alpha256(alpha)); 653 | let s = SolidShader { color }; 654 | ShaderStorage::Solid(s) 655 | } 656 | Source::Image(ref image, ExtendMode::Pad, filter, transform) => { 657 | if let Some(offset) = is_integer_transform(&ti.then(&transform)) { 658 | ShaderStorage::ImagePadAlpha(ImagePadAlphaShader::new(image, offset.x, offset.y, alpha)) 659 | } else { 660 | if alpha != 255 { 661 | if *filter == FilterMode::Bilinear { 662 | let s = TransformedImageAlphaShader::::new(image, &ti.then(&transform), alpha); 663 | ShaderStorage::TransformedPadImageAlpha(s) 664 | } else { 665 | let s = TransformedNearestImageAlphaShader::::new(image, &ti.then(&transform), alpha); 666 | ShaderStorage::TransformedNearestPadImageAlpha(s) 667 | } 668 | } else { 669 | if *filter == FilterMode::Bilinear { 670 | let s = TransformedImageShader::::new(image, &ti.then(&transform)); 671 | ShaderStorage::TransformedPadImage(s) 672 | } else { 673 | let s = TransformedNearestImageShader::::new(image, &ti.then(&transform)); 674 | ShaderStorage::TransformedNearestPadImage(s) 675 | } 676 | } 677 | } 678 | } 679 | Source::Image(ref image, ExtendMode::Repeat, filter, transform) => { 680 | if let Some(offset) = is_integer_transform(&ti.then(&transform)) { 681 | ShaderStorage::ImageRepeatAlpha(ImageRepeatAlphaShader::new(image, offset.x, offset.y, alpha)) 682 | } else { 683 | if *filter == FilterMode::Bilinear { 684 | if alpha != 255 { 685 | let s = TransformedImageAlphaShader::::new(image, &ti.then(&transform), alpha); 686 | ShaderStorage::TransformedRepeatImageAlpha(s) 687 | } else { 688 | let s = TransformedImageShader::::new(image, &ti.then(&transform)); 689 | ShaderStorage::TransformedRepeatImage(s) 690 | } 691 | } else { 692 | if alpha != 255 { 693 | let s = TransformedNearestImageAlphaShader::::new(image, &ti.then(&transform), alpha); 694 | ShaderStorage::TransformedNearestRepeatImageAlpha(s) 695 | } else { 696 | let s = TransformedNearestImageShader::::new(image, &ti.then(&transform)); 697 | ShaderStorage::TransformedNearestRepeatImage(s) 698 | } 699 | } 700 | } 701 | } 702 | Source::RadialGradient(ref gradient, spread, transform) => { 703 | let s = RadialGradientShader::new(gradient, &ti.then(&transform), *spread, alpha); 704 | ShaderStorage::RadialGradient(s) 705 | } 706 | Source::TwoCircleRadialGradient(ref gradient, spread, c1, r1, c2, r2, transform) => { 707 | let s = TwoCircleRadialGradientShader::new(gradient, &ti.then(&transform), *c1, *r1, *c2, *r2, *spread, alpha); 708 | ShaderStorage::TwoCircleRadialGradient(s) 709 | } 710 | Source::SweepGradient(ref gradient, spread, start_angle, end_angle, transform) => { 711 | let s = SweepGradientShader::new(gradient, &ti.then(&transform), *start_angle, *end_angle, *spread, alpha); 712 | ShaderStorage::SweepGradient(s) 713 | } 714 | Source::LinearGradient(ref gradient, spread, transform) => { 715 | let s = LinearGradientShader::new(gradient, &ti.then(&transform), *spread, alpha); 716 | ShaderStorage::LinearGradient(s) 717 | } 718 | }; 719 | 720 | match shader_storage { 721 | ShaderStorage::None => unreachable!(), 722 | ShaderStorage::Solid(s) => s, 723 | ShaderStorage::ImagePadAlpha(s) => s, 724 | ShaderStorage::ImageRepeatAlpha(s) => s, 725 | ShaderStorage::TransformedNearestPadImageAlpha(s) => s, 726 | ShaderStorage::TransformedNearestRepeatImageAlpha(s) => s, 727 | ShaderStorage::TransformedPadImageAlpha(s) => s, 728 | ShaderStorage::TransformedRepeatImageAlpha(s) => s, 729 | ShaderStorage::TransformedPadImage(s) => s, 730 | ShaderStorage::TransformedRepeatImage(s) => s, 731 | ShaderStorage::TransformedNearestPadImage(s) => s, 732 | ShaderStorage::TransformedNearestRepeatImage(s) => s, 733 | ShaderStorage::RadialGradient(s) => s, 734 | ShaderStorage::TwoCircleRadialGradient(s) => s, 735 | ShaderStorage::SweepGradient(s) => s, 736 | ShaderStorage::LinearGradient(s) => s, 737 | } 738 | } 739 | 740 | pub enum ShaderBlitterStorage<'a> { 741 | None, 742 | ShaderBlendMaskBlitter(ShaderBlendMaskBlitter<'a>), 743 | ShaderClipBlendMaskBlitter(ShaderClipBlendMaskBlitter<'a>), 744 | ShaderMaskBlitter(ShaderMaskBlitter<'a>), 745 | ShaderClipMaskBlitter(ShaderClipMaskBlitter<'a>), 746 | ShaderBlendBlitter(ShaderBlendBlitter<'a>), 747 | } 748 | 749 | /* 750 | pub struct SolidBlitter<'a> { 751 | color: u32, 752 | mask: &'a [u8], 753 | dest: &'a mut [u32], 754 | dest_stride: i32, 755 | mask_stride: i32, 756 | } 757 | 758 | impl<'a> Blitter for SolidBlitter<'a> { 759 | fn blit_span(&mut self, y: i32, x1: i32, x2: i32) { 760 | let dest_row = y * self.dest_stride; 761 | let mask_row = y * self.mask_stride; 762 | for i in x1..x2 { 763 | self.dest[(dest_row + i) as usize] = over_in( 764 | self.color, 765 | self.dest[(dest_row + i) as usize], 766 | self.mask[(mask_row + i) as usize] as u32, 767 | ); 768 | } 769 | } 770 | } 771 | */ 772 | -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | 4 | use crate::geom::intrect; 5 | use crate::*; 6 | const WHITE_SOURCE: Source = Source::Solid(SolidSource { 7 | r: 0xff, 8 | g: 0xff, 9 | b: 0xff, 10 | a: 0xff, 11 | }); 12 | 13 | #[test] 14 | fn basic_rasterizer() { 15 | let mut dt = DrawTarget::new(2, 2); 16 | let mut pb = PathBuilder::new(); 17 | pb.rect(1., 1., 1., 1.); 18 | dt.fill( 19 | &pb.finish(), 20 | &WHITE_SOURCE, 21 | &DrawOptions::new(), 22 | ); 23 | let white = 0xffffffff; 24 | assert_eq!(dt.get_data(), &vec![0, 0, 0, white][..]) 25 | } 26 | 27 | #[test] 28 | fn implicit_close() { 29 | let mut dt = DrawTarget::new(2, 2); 30 | let mut pb = PathBuilder::new(); 31 | pb.move_to(1., 1.); 32 | pb.line_to(2., 1.); 33 | pb.line_to(2., 2.); 34 | pb.line_to(1., 2.); 35 | dt.fill( 36 | &pb.finish(), 37 | &WHITE_SOURCE, 38 | &DrawOptions::new(), 39 | ); 40 | let white = 0xffffffff; 41 | assert_eq!(dt.get_data(), &vec![0, 0, 0, white][..]) 42 | } 43 | 44 | #[test] 45 | fn offscreen_edges() { 46 | let mut dt = DrawTarget::new(2, 2); 47 | let mut pb = PathBuilder::new(); 48 | pb.rect(1., 0., 8., 1.); 49 | dt.fill( 50 | &pb.finish(), 51 | &WHITE_SOURCE, 52 | &DrawOptions::new(), 53 | ); 54 | let white = 0xffffffff; 55 | assert_eq!(dt.get_data(), &vec![0, white, 0, 0][..]) 56 | } 57 | 58 | #[test] 59 | fn clip_rect() { 60 | let mut dt = DrawTarget::new(2, 2); 61 | dt.push_clip_rect(intrect(1, 1, 2, 2)); 62 | let mut pb = PathBuilder::new(); 63 | pb.rect(0., 0., 2., 2.); 64 | dt.fill( 65 | &pb.finish(), 66 | &WHITE_SOURCE, 67 | &DrawOptions::new(), 68 | ); 69 | let white = 0xffffffff; 70 | assert_eq!(dt.get_data(), &vec![0, 0, 0, white][..]) 71 | } 72 | 73 | #[test] 74 | fn nested_clip_rect() { 75 | let mut dt = DrawTarget::new(2, 2); 76 | dt.push_clip_rect(intrect(0, 1, 2, 2)); 77 | dt.push_clip_rect(intrect(1, 0, 2, 2)); 78 | let mut pb = PathBuilder::new(); 79 | pb.rect(0., 0., 2., 2.); 80 | dt.fill( 81 | &pb.finish(), 82 | &WHITE_SOURCE, 83 | &DrawOptions::new(), 84 | ); 85 | let white = 0xffffffff; 86 | assert_eq!(dt.get_data(), &vec![0, 0, 0, white][..]) 87 | } 88 | 89 | #[test] 90 | fn even_odd_rect() { 91 | let mut dt = DrawTarget::new(2, 2); 92 | let mut pb = PathBuilder::new(); 93 | pb.rect(0., 0., 2., 2.); 94 | pb.rect(0., 0., 2., 2.); 95 | pb.rect(1., 1., 2., 2.); 96 | let mut path = pb.finish(); 97 | path.winding = Winding::EvenOdd; 98 | dt.fill( 99 | &path, 100 | &WHITE_SOURCE, 101 | &DrawOptions::new(), 102 | ); 103 | let white = 0xffffffff; 104 | assert_eq!(dt.get_data(), &vec![0, 0, 0, white][..]) 105 | } 106 | 107 | #[test] 108 | fn clear() { 109 | let mut dt = DrawTarget::new(2, 2); 110 | let mut pb = PathBuilder::new(); 111 | pb.rect(0., 0., 2., 2.); 112 | let path = pb.finish(); 113 | dt.fill( 114 | &path, 115 | &WHITE_SOURCE, 116 | &DrawOptions::new(), 117 | ); 118 | dt.clear(SolidSource { 119 | r: 0, 120 | g: 0, 121 | b: 0, 122 | a: 0, 123 | }); 124 | assert_eq!(dt.get_data(), &vec![0, 0, 0, 0][..]) 125 | } 126 | 127 | #[test] 128 | fn basic_push_layer() { 129 | let mut dt = DrawTarget::new(2, 2); 130 | let mut pb = PathBuilder::new(); 131 | dt.push_clip_rect(intrect(1, 1, 2, 2)); 132 | dt.push_layer(1.); 133 | pb.rect(1., 1., 1., 1.); 134 | dt.fill( 135 | &pb.finish(), 136 | &WHITE_SOURCE, 137 | &DrawOptions::new(), 138 | ); 139 | let white = 0xffffffff; 140 | dt.pop_layer(); 141 | assert_eq!(dt.get_data(), &vec![0, 0, 0, white][..]) 142 | } 143 | 144 | #[test] 145 | fn basic_draw_image() { 146 | let mut dt = DrawTarget::new(2, 2); 147 | let mut dt2 = DrawTarget::new(1, 1); 148 | 149 | let mut pb = PathBuilder::new(); 150 | pb.rect(0., 0., 1., 1.); 151 | dt2.fill( 152 | &pb.finish(), 153 | &WHITE_SOURCE, 154 | &DrawOptions::new(), 155 | ); 156 | let image = Image { 157 | width: 1, 158 | height: 1, 159 | data: dt2.get_data(), 160 | }; 161 | dt.draw_image_at(1., 1., &image, &DrawOptions::default()); 162 | let white = 0xffffffff; 163 | assert_eq!(dt.get_data(), &vec![0, 0, 0, white][..]) 164 | } 165 | 166 | #[test] 167 | fn draw_image_subset() { 168 | let mut dt = DrawTarget::new(2, 2); 169 | let mut dt2 = DrawTarget::new(3, 3); 170 | 171 | let mut pb = PathBuilder::new(); 172 | pb.rect(1., 1., 1., 1.); 173 | dt2.fill( 174 | &pb.finish(), 175 | &WHITE_SOURCE, 176 | &DrawOptions::new(), 177 | ); 178 | let image = Image { 179 | width: 3, 180 | height: 3, 181 | data: dt2.get_data(), 182 | }; 183 | dt.draw_image_at(0., 0., &image, &DrawOptions::default()); 184 | let white = 0xffffffff; 185 | assert_eq!(dt.get_data(), &vec![0, 0, 0, white][..]) 186 | } 187 | 188 | #[test] 189 | fn repeating_draw_image() { 190 | let mut dt = DrawTarget::new(4, 1); 191 | let mut dt2 = DrawTarget::new(2, 1); 192 | 193 | let mut pb = PathBuilder::new(); 194 | pb.rect(0., 0., 1., 1.); 195 | dt2.fill( 196 | &pb.finish(), 197 | &WHITE_SOURCE, 198 | &DrawOptions::new(), 199 | ); 200 | let image = Image { 201 | width: 2, 202 | height: 1, 203 | data: dt2.get_data(), 204 | }; 205 | let mut pb = PathBuilder::new(); 206 | pb.rect(0., 0., 4., 1.); 207 | let source = Source::Image( 208 | image, 209 | ExtendMode::Repeat, 210 | FilterMode::Bilinear, 211 | Transform::translation(0., 0.), 212 | ); 213 | 214 | dt.fill(&pb.finish(), &source, &DrawOptions::default()); 215 | let white = 0xffffffff; 216 | assert_eq!(dt.get_data(), &vec![white, 0, white, 0][..]) 217 | } 218 | 219 | #[test] 220 | fn stroke() { 221 | let mut dt = DrawTarget::new(3, 3); 222 | let mut pb = PathBuilder::new(); 223 | pb.rect(0.5, 0.5, 2., 2.); 224 | dt.stroke( 225 | &pb.finish(), 226 | &WHITE_SOURCE, 227 | &StrokeStyle { 228 | width: 1., 229 | ..Default::default() 230 | }, 231 | &DrawOptions::new(), 232 | ); 233 | let white = 0xffffffff; 234 | assert_eq!( 235 | dt.get_data(), 236 | &vec![white, white, white, white, 0, white, white, white, white][..] 237 | ) 238 | } 239 | 240 | #[test] 241 | fn degenerate_stroke() { 242 | let mut dt = DrawTarget::new(3, 3); 243 | let mut pb = PathBuilder::new(); 244 | pb.move_to(0.5, 0.5); 245 | pb.line_to(2., 2.); 246 | pb.line_to(2., 2.); 247 | pb.line_to(4., 2.); 248 | dt.stroke( 249 | &pb.finish(), 250 | &WHITE_SOURCE, 251 | &StrokeStyle { 252 | width: 1., 253 | ..Default::default() 254 | }, 255 | &DrawOptions::new(), 256 | ); 257 | } 258 | 259 | #[test] 260 | fn degenerate_stroke2() { 261 | let mut dt = DrawTarget::new(3, 3); 262 | let mut pb = PathBuilder::new(); 263 | pb.move_to(2., 2.); 264 | pb.line_to(2., 3.); 265 | pb.line_to(2., 4.); 266 | dt.stroke( 267 | &pb.finish(), 268 | &WHITE_SOURCE, 269 | &StrokeStyle { 270 | width: 1., 271 | ..Default::default() 272 | }, 273 | &DrawOptions::new(), 274 | ); 275 | } 276 | 277 | #[test] 278 | fn zero_width_stroke() { 279 | let mut dt = DrawTarget::new(3, 3); 280 | let mut pb = PathBuilder::new(); 281 | pb.move_to(2., 2.); 282 | pb.line_to(200., 300.); 283 | dt.stroke( 284 | &pb.finish(), 285 | &WHITE_SOURCE, 286 | &StrokeStyle { 287 | width: 0., 288 | ..Default::default() 289 | }, 290 | &DrawOptions::new(), 291 | ); 292 | } 293 | 294 | #[test] 295 | fn negative_width_stroke() { 296 | let mut dt = DrawTarget::new(3, 3); 297 | let mut pb = PathBuilder::new(); 298 | pb.move_to(2., 2.); 299 | pb.line_to(200., 300.); 300 | dt.stroke( 301 | &pb.finish(), 302 | &WHITE_SOURCE, 303 | &StrokeStyle { 304 | width: std::f32::MIN, 305 | ..Default::default() 306 | }, 307 | &DrawOptions::new(), 308 | ); 309 | } 310 | 311 | #[test] 312 | fn tiny_negative_width_stroke() { 313 | let mut dt = DrawTarget::new(3, 3); 314 | let mut pb = PathBuilder::new(); 315 | pb.move_to(2., 2.); 316 | pb.line_to(200., 300.); 317 | dt.stroke( 318 | &pb.finish(), 319 | &WHITE_SOURCE, 320 | &StrokeStyle { 321 | width: -core::f32::MIN_POSITIVE, 322 | ..Default::default() 323 | }, 324 | &DrawOptions::new(), 325 | ); 326 | } 327 | 328 | #[test] 329 | fn dashing() { 330 | let mut dt = DrawTarget::new(3, 3); 331 | let mut pb = PathBuilder::new(); 332 | pb.move_to(40., 40.); 333 | pb.line_to(160., 40.); 334 | pb.line_to(160., 160.); 335 | pb.line_to(160., 160.); 336 | pb.close(); 337 | dt.stroke( 338 | &pb.finish(), 339 | &WHITE_SOURCE, 340 | &StrokeStyle { 341 | width: 1., 342 | dash_array: vec![10.0, 6.0, 4.0, 10.0, 6.0, 4.0], 343 | dash_offset: 15.0, 344 | ..Default::default() 345 | }, 346 | &DrawOptions::new(), 347 | ); 348 | } 349 | 350 | #[test] 351 | fn dash_rect() { 352 | let mut dt = DrawTarget::new(3, 3); 353 | let mut pb = PathBuilder::new(); 354 | pb.rect(0.5, 0.5, 12., 12.); 355 | dt.stroke( 356 | &pb.finish(), 357 | &WHITE_SOURCE, 358 | &StrokeStyle { 359 | width: 1., 360 | dash_array: vec![1., 1.], 361 | dash_offset: 0.5, 362 | ..Default::default() 363 | }, 364 | &DrawOptions::new(), 365 | ); 366 | let white = 0xffffffff; 367 | assert_eq!(dt.get_data(), &vec![white, 0, white, 0, 0, 0, white, 0, 0][..]) 368 | } 369 | 370 | #[test] 371 | fn dash_with_negative_length_1() { 372 | let mut dt = DrawTarget::new(3, 3); 373 | let mut pb = PathBuilder::new(); 374 | pb.rect(0.5, 0.5, 12., 12.); 375 | dt.stroke( 376 | &pb.finish(), 377 | &WHITE_SOURCE, 378 | &StrokeStyle { 379 | width: 1., 380 | dash_array: vec![-1.], 381 | dash_offset: 0.5, 382 | ..Default::default() 383 | }, 384 | &DrawOptions::new(), 385 | ); 386 | // Must not loop. 387 | } 388 | 389 | #[test] 390 | fn dash_with_negative_length_2() { 391 | let mut dt = DrawTarget::new(3, 3); 392 | let mut pb = PathBuilder::new(); 393 | pb.rect(0.5, 0.5, 12., 12.); 394 | dt.stroke( 395 | &pb.finish(), 396 | &WHITE_SOURCE, 397 | &StrokeStyle { 398 | width: 1., 399 | dash_array: vec![5., -10.], 400 | dash_offset: 0.5, 401 | ..Default::default() 402 | }, 403 | &DrawOptions::new(), 404 | ); 405 | // Must not loop. 406 | } 407 | 408 | #[test] 409 | fn draw_options_alpha() { 410 | let mut dt = DrawTarget::new(2, 2); 411 | let mut pb = PathBuilder::new(); 412 | pb.rect(1., 1., 1., 1.); 413 | dt.fill( 414 | &pb.finish(), 415 | &WHITE_SOURCE, 416 | &DrawOptions { 417 | alpha: 0., 418 | ..Default::default() 419 | }, 420 | ); 421 | assert_eq!(dt.get_data(), &vec![0, 0, 0, 0][..]) 422 | } 423 | 424 | #[test] 425 | fn blend_zero() { 426 | let mut dt = DrawTarget::new(2, 2); 427 | 428 | dt.clear(SolidSource { 429 | r: 0xff, 430 | g: 0xff, 431 | b: 0xff, 432 | a: 0xff, 433 | }); 434 | 435 | let source = Source::Solid(SolidSource { 436 | r: 0x00, 437 | g: 0x00, 438 | b: 0x00, 439 | a: 0xff, 440 | }); 441 | 442 | let mut pb = PathBuilder::new(); 443 | pb.rect(0., 0., 1., 1.); 444 | let path = pb.finish(); 445 | dt.fill(&path, &source, &DrawOptions::default()); 446 | let white = 0xffffffff; 447 | let black = 0xff000000; 448 | 449 | assert_eq!(dt.get_data(), &vec![black, white, white, white][..]) 450 | } 451 | 452 | #[test] 453 | fn two_circle_radial_gradient() { 454 | let mut dt = DrawTarget::new(2, 2); 455 | 456 | let gradient = Source::new_two_circle_radial_gradient( 457 | Gradient { 458 | stops: vec![ 459 | GradientStop { 460 | position: 0.0, 461 | color: Color::new(0xff, 00, 00, 00), 462 | }, 463 | GradientStop { 464 | position: 1.0, 465 | color: Color::new(0xff, 0xff, 0xff, 0xff), 466 | }, 467 | ], 468 | }, 469 | Point::new(-8., -8.), 470 | 0.0, 471 | Point::new(-8., -8.), 472 | 0.5, 473 | Spread::Pad, 474 | ); 475 | 476 | let mut pb = PathBuilder::new(); 477 | pb.rect(0., 0., 2., 2.); 478 | let path = pb.finish(); 479 | dt.fill(&path, &gradient, &DrawOptions::default()); 480 | let white = 0xffffffff; 481 | 482 | assert_eq!(dt.get_data(), &vec![white, white, white, white][..]) 483 | } 484 | 485 | #[test] 486 | fn two_circle_radial_gradient_boundary() { 487 | // make sure the gradient doesn't draw anything for r < 0 circles 488 | let mut dt = DrawTarget::new(2, 2); 489 | let gradient = Gradient { 490 | stops: vec![ 491 | GradientStop { 492 | position: 0.0, 493 | color: Color::new(0xff, 0xff, 0, 0xff), 494 | }, 495 | GradientStop { 496 | position: 1.0, 497 | color: Color::new(0xff, 0x0, 0, 0xff), 498 | }, 499 | ], 500 | }; 501 | 502 | let src = Source::new_two_circle_radial_gradient( 503 | gradient.clone(), 504 | Point::new(150., 25.), 505 | 50., 506 | Point::new(200., 25.), 507 | 100., 508 | Spread::Pad, 509 | ); 510 | 511 | dt.fill_rect(0., 0., 2., 2., &src, &DrawOptions::default()); 512 | assert_eq!(dt.get_data(), &vec![0, 0, 0, 0][..]); 513 | 514 | let src = Source::new_two_circle_radial_gradient( 515 | gradient, 516 | Point::new(100., 25.), 517 | 50., 518 | Point::new(200., 25.), 519 | 100., 520 | Spread::Pad, 521 | ); 522 | 523 | dt.fill_rect(0., 0., 2., 2., &src, &DrawOptions::default()); 524 | assert_eq!(dt.get_data(), &vec![0, 0, 0, 0][..]) 525 | } 526 | 527 | #[test] 528 | fn get_mut_data() { 529 | let mut dt = DrawTarget::new(1, 1); 530 | 531 | let data = dt.get_data_u8_mut(); 532 | data[0] = 0xff; 533 | data[1] = 0xff; 534 | data[2] = 0xff; 535 | data[3] = 0xff; 536 | 537 | let white = 0xffffffff; 538 | 539 | assert_eq!(dt.get_data(), &vec![white][..]) 540 | } 541 | 542 | #[test] 543 | fn draw_options_aliased() { 544 | let mut dt = DrawTarget::new(2, 2); 545 | let mut pb = PathBuilder::new(); 546 | pb.rect(0.5, 0.5, 1., 1.); 547 | dt.fill( 548 | &pb.finish(), 549 | &WHITE_SOURCE, 550 | &DrawOptions { 551 | antialias: AntialiasMode::None, 552 | ..Default::default() 553 | }, 554 | ); 555 | let white = 0xffffffff; 556 | assert_eq!(dt.get_data(), &vec![0, 0, white, 0][..]) 557 | } 558 | 559 | #[test] 560 | fn draw_options_aliased_bounds() { 561 | let mut dt = DrawTarget::new(2, 4); 562 | let mut pb = PathBuilder::new(); 563 | pb.rect(1., 3., 1., 1.); 564 | dt.fill( 565 | &pb.finish(), 566 | &WHITE_SOURCE, 567 | &DrawOptions { 568 | antialias: AntialiasMode::None, 569 | ..Default::default() 570 | }, 571 | ); 572 | } 573 | 574 | #[test] 575 | fn copy_surface() { 576 | let mut dest = DrawTarget::new(2, 2); 577 | let mut src = DrawTarget::new(2, 2); 578 | 579 | let white = 0xffffffff; 580 | let red = 0xffff0000; 581 | let green = 0xff00ff00; 582 | let blue = 0xff0000ff; 583 | 584 | let data = src.get_data_mut(); 585 | data[0] = white; 586 | data[1] = red; 587 | data[2] = green; 588 | data[3] = blue; 589 | 590 | dest.copy_surface(&src, intrect(0, 0, 2, 2), IntPoint::new(-1, -1)); 591 | assert_eq!(dest.get_data(), &vec![blue, 0, 0, 0][..]); 592 | dest.copy_surface(&src, intrect(0, 0, 2, 2), IntPoint::new(1, -1)); 593 | assert_eq!(dest.get_data(), &vec![blue, green, 0, 0][..]); 594 | dest.copy_surface(&src, intrect(0, 0, 2, 2), IntPoint::new(-1, 1)); 595 | assert_eq!(dest.get_data(), &vec![blue, green, red, 0][..]); 596 | dest.copy_surface(&src, intrect(0, 0, 2, 2), IntPoint::new(1, 1)); 597 | assert_eq!(dest.get_data(), &vec![blue, green, red, white][..]); 598 | } 599 | 600 | #[test] 601 | fn path_contains_point() { 602 | 603 | let mut pb = PathBuilder::new(); 604 | pb.rect(0., 0., 2., 2.); 605 | let rect = pb.finish(); 606 | 607 | assert!(rect.contains_point(0.1, 1., 1.)); 608 | assert!(!rect.contains_point(0.1, 4., 4.)); 609 | assert!(rect.contains_point(0.1, 0., 1.)); 610 | 611 | let mut pb = PathBuilder::new(); 612 | pb.move_to(0., 0.); 613 | pb.line_to(0., 1.); 614 | pb.line_to(1., 1.); 615 | pb.close(); 616 | let tri = pb.finish(); 617 | 618 | assert!(tri.contains_point(0.1, 0.5, 0.5)); 619 | assert!(!tri.contains_point(0.1, 0.6, 0.5)); 620 | assert!(tri.contains_point(0.1, 0.4, 0.5)); 621 | } 622 | 623 | #[test] 624 | fn push_clip() { 625 | 626 | let mut dest = DrawTarget::new(2, 2); 627 | 628 | let mut pb = PathBuilder::new(); 629 | pb.rect(1., 1., 1., 1.); 630 | let rect = pb.finish(); 631 | 632 | dest.push_clip(&rect); 633 | 634 | let mut pb = PathBuilder::new(); 635 | pb.rect(0., 0., 1., 1.); 636 | let rect = pb.finish(); 637 | 638 | dest.push_clip(&rect); 639 | } 640 | 641 | #[test] 642 | fn empty_lineto_fill() { 643 | let mut dt = DrawTarget::new(2, 2); 644 | 645 | let mut pb = PathBuilder::new(); 646 | pb.line_to(0., 2.); 647 | pb.line_to(2., 2.); 648 | pb.line_to(2., 0.); 649 | dt.fill( 650 | &pb.finish(), 651 | &WHITE_SOURCE, 652 | &DrawOptions::new(), 653 | ); 654 | assert_eq!(dt.get_data()[0], 0) 655 | } 656 | 657 | #[test] 658 | fn empty_lineto_stroke() { 659 | let mut dt = DrawTarget::new(2, 2); 660 | 661 | let mut pb = PathBuilder::new(); 662 | pb.line_to(0., 2.); 663 | let path = pb.finish(); 664 | dt.stroke( 665 | &path, 666 | &WHITE_SOURCE, 667 | &StrokeStyle { 668 | width: 2., 669 | ..Default::default() 670 | }, 671 | &DrawOptions::new(), 672 | ); 673 | assert_eq!(dt.get_data(), &vec![0, 0, 0, 0][..]); 674 | 675 | dt.stroke( 676 | &path, 677 | &WHITE_SOURCE, 678 | &StrokeStyle { 679 | width: 2., 680 | dash_array: vec![2., 2.], 681 | dash_offset: 0., 682 | ..Default::default() 683 | }, 684 | &DrawOptions::new(), 685 | ); 686 | assert_eq!(dt.get_data(), &vec![0, 0, 0, 0][..]); 687 | } 688 | 689 | #[test] 690 | fn clip_rect_composite() { 691 | 692 | let mut dest = DrawTarget::new(2, 2); 693 | 694 | let mut pb = PathBuilder::new(); 695 | pb.rect(0., 0., 2., 2.); 696 | let rect = pb.finish(); 697 | 698 | let fill = Source::Solid(SolidSource { 699 | r: 255, 700 | g: 0, 701 | b: 0, 702 | a: 255, 703 | }); 704 | dest.fill(&rect, &fill, &DrawOptions::new()); 705 | dest.push_clip(&rect); 706 | 707 | let pixels = dest.get_data(); 708 | // expected a red pixel 709 | let expected = 710 | 255 << 24 | 711 | 255 << 16 | 712 | 0 << 8 | 713 | 0; 714 | assert_eq!(pixels[0], expected); 715 | 716 | let fill = Source::Solid(SolidSource { 717 | r: 0, 718 | g: 255, 719 | b: 0, 720 | a: 255, 721 | }); 722 | dest.fill(&rect, &fill, &DrawOptions::new()); 723 | let pixels = dest.get_data(); 724 | // expected a green pixel 725 | let expected = 726 | 255 << 24 | 727 | 0 << 16 | 728 | 255 << 8 | 729 | 0; 730 | assert_eq!(pixels[0], expected); 731 | } 732 | 733 | #[test] 734 | fn draw_image_xor() { 735 | let mut target = DrawTarget::new(1, 1); 736 | let mut pb = PathBuilder::new(); 737 | pb.rect(0., 0., 1., 1.); 738 | let rect = pb.finish(); 739 | let mut options = DrawOptions::new(); 740 | options.blend_mode = BlendMode::Src; 741 | target.fill( 742 | &rect, 743 | &Source::Solid(SolidSource::from_unpremultiplied_argb(128, 0, 255, 255)), 744 | 745 | &options, 746 | ); 747 | 748 | options.blend_mode = BlendMode::Xor; 749 | target.fill( 750 | &rect, 751 | &Source::Solid(SolidSource::from_unpremultiplied_argb(0xbf, 255, 255, 0)), 752 | 753 | &options, 754 | ); 755 | } 756 | 757 | #[test] 758 | fn arc_contains() { 759 | let mut pb = PathBuilder::new(); 760 | pb.arc(50., 25., 10., 0., std::f32::consts::PI); 761 | let path = pb.finish(); 762 | assert!(!path.contains_point(0.1, 50., 10.)); 763 | assert!(!path.contains_point(0.1, 50., 20.)); 764 | assert!(path.contains_point(0.1, 50., 30.)); 765 | assert!(!path.contains_point(0.1, 50., 40.)); 766 | assert!(!path.contains_point(0.1, 30., 20.)); 767 | assert!(!path.contains_point(0.1, 70., 20.)); 768 | assert!(!path.contains_point(0.1, 30., 30.)); 769 | assert!(!path.contains_point(0.1, 70., 30.)); 770 | } 771 | 772 | #[test] 773 | fn new_linear_gradient_zerosize() { 774 | let source = Source::new_linear_gradient( 775 | Gradient { stops: Vec::new() }, 776 | Point::new(42., 42.), 777 | Point::new(42., 42.), 778 | Spread::Pad, 779 | ); 780 | 781 | match source { 782 | Source::LinearGradient(_, _, transform) => { 783 | assert_eq!(transform.m11, 0.); 784 | assert_eq!(transform.m12, 0.); 785 | assert_eq!(transform.m21, 0.); 786 | assert_eq!(transform.m22, 0.); 787 | assert_eq!(transform.m31, 0.); 788 | assert_eq!(transform.m32, 0.); 789 | }, 790 | _ => panic!("dead end"), 791 | }; 792 | } 793 | 794 | #[test] 795 | fn blend_surface_with_negative_offset() { 796 | let mut dt1 = crate::DrawTarget::new(3, 3); 797 | let dt2 = crate::DrawTarget::new(3, 3); 798 | dt1.blend_surface_with_alpha( 799 | &dt2, 800 | crate::IntRect::new(crate::IntPoint::new(-1, -1), crate::IntPoint::new(2, 2)), 801 | crate::IntPoint::new(2, 2), 802 | 1.0, 803 | ); 804 | } 805 | 806 | #[test] 807 | fn close_sets_current_point() { 808 | let mut dt = crate::DrawTarget::new(3, 3); 809 | let mut pb = PathBuilder::new(); 810 | pb.rect(10., 10., 100., 100.); 811 | pb.line_to(-10., -10.); 812 | let path = pb.finish(); 813 | dt.stroke( 814 | &path, 815 | &WHITE_SOURCE, 816 | &StrokeStyle { 817 | width: 5., 818 | ..Default::default() 819 | }, 820 | &DrawOptions::new(), 821 | ); 822 | let white = 0xffffffff; 823 | assert_eq!( 824 | dt.get_data(), 825 | &vec![white, white, white, white, white, white, white, white, white][..] 826 | ); 827 | 828 | // make sure we get the same behaviour when dashing 829 | let mut dt = crate::DrawTarget::new(3, 3); 830 | dt.stroke( 831 | &path, 832 | &WHITE_SOURCE, 833 | &StrokeStyle { 834 | width: 5., 835 | dash_array: vec![1000., 10.], 836 | ..Default::default() 837 | }, 838 | &DrawOptions::new(), 839 | ); 840 | let white = 0xffffffff; 841 | assert_eq!( 842 | dt.get_data(), 843 | &vec![white, white, white, white, white, white, white, white, white][..] 844 | ); 845 | } 846 | 847 | #[test] 848 | fn close_clears_start_point() { 849 | let mut dt = DrawTarget::new(4, 4); 850 | 851 | // This path is positioned just outside of the draw 852 | // target so that when it's stroked we can ensure 853 | // that none of the stroke leaks in 854 | let mut pb = PathBuilder::new(); 855 | pb.move_to(14.0, 0.0); 856 | pb.line_to(104.0, 30.0); 857 | pb.line_to(14.0, 70.0); 858 | pb.close(); 859 | 860 | let style = StrokeStyle { 861 | width: 20.0, 862 | cap: LineCap::Square, 863 | join: LineJoin::Bevel, 864 | ..StrokeStyle::default() 865 | }; 866 | 867 | dt.stroke(&pb.finish(), &WHITE_SOURCE, &style, &DrawOptions::new()); 868 | 869 | for i in dt.get_data() { 870 | assert_eq!(*i, 0); 871 | } 872 | } 873 | 874 | #[test] 875 | fn negative_repeat() { 876 | let mut dt = DrawTarget::new(2, 2); 877 | let options = DrawOptions::new(); 878 | 879 | let img = Image { 880 | width: 2, 881 | height: 2, 882 | data: &vec![0xffff0000; 2*2], 883 | }; 884 | 885 | let identity = Transform::identity(); 886 | let img_src = Source::Image(img, ExtendMode::Repeat, FilterMode::Nearest, identity); 887 | dt.set_transform(&dt.get_transform().pre_translate(euclid::vec2(0., 1.))); 888 | dt.fill_rect(0., -1., 100., 50., &img_src, &options); 889 | } 890 | 891 | #[test] 892 | fn zero_sized_draw_target() { 893 | let mut dt = DrawTarget::new(0, 0); 894 | let mut pb = PathBuilder::new(); 895 | pb.line_to(0.5, 0.5); 896 | pb.line_to(2.0, -2.0); 897 | pb.line_to(2.0, 0.5); 898 | dt.fill( 899 | &pb.finish(), 900 | &WHITE_SOURCE, 901 | &DrawOptions::new(), 902 | ); 903 | } 904 | 905 | #[test] 906 | fn dash_subpath_restart() { 907 | let mut dt = DrawTarget::new(2, 2); 908 | let mut pb = PathBuilder::new(); 909 | 910 | // move to some arbitrary place 911 | pb.move_to(40., 40.); 912 | pb.line_to(60., 40.); 913 | // the dash should still be off 914 | 915 | // Start a new subpath. This should reset the dash state so that we 916 | // cover the entire draw target with the dash 917 | pb.move_to(-0.5, 1.); 918 | pb.line_to(3., 1.); 919 | 920 | dt.stroke( 921 | &pb.finish(), 922 | &WHITE_SOURCE, 923 | &StrokeStyle { 924 | width: 2., 925 | dash_array: vec![1.0, 14.0, 3.0, 80.], 926 | dash_offset: 15.0, 927 | ..Default::default() 928 | }, 929 | &DrawOptions::new(), 930 | ); 931 | 932 | let white = 0xffffffff; 933 | assert_eq!( 934 | dt.get_data(), 935 | &vec![white, white, white, white][..] 936 | ); 937 | } 938 | 939 | #[test] 940 | fn nearest_offset() { 941 | let white = 0xffffffff; 942 | let checkerboard = vec![0xff000000, white, white, 0xff000000]; 943 | let (width, height ) = (2, 2); 944 | 945 | let mut dt = DrawTarget::new(width, height); 946 | let image = Image { width, height, data: &checkerboard }; 947 | dt.set_transform(&Transform::translation(-299., -299.)); 948 | let source = Source::Image(image, ExtendMode::Pad, FilterMode::Nearest, 949 | Transform::identity().then_scale(width as f32 / 600., height as f32 / 600.)); 950 | 951 | // draw a checkerboard scaled way up and make sure the origin stays in the center 952 | dt.fill_rect(0., 0., 600., 600., &source, &DrawOptions::new()); 953 | 954 | assert_eq!( 955 | dt.get_data(), 956 | &checkerboard[..] 957 | ); 958 | } 959 | } 960 | -------------------------------------------------------------------------------- /src/draw_target.rs: -------------------------------------------------------------------------------- 1 | use crate::rasterizer::Rasterizer; 2 | 3 | use crate::blitter::*; 4 | use sw_composite::*; 5 | 6 | use crate::dash::*; 7 | use crate::geom::*; 8 | use crate::path_builder::*; 9 | 10 | pub use crate::path_builder::Winding; 11 | use lyon_geom::CubicBezierSegment; 12 | 13 | #[cfg(feature = "text")] 14 | mod fk { 15 | pub use font_kit::canvas::{Canvas, Format, RasterizationOptions}; 16 | pub use font_kit::font::Font; 17 | pub use font_kit::hinting::HintingOptions; 18 | pub use pathfinder_geometry::transform2d::Transform2F; 19 | pub use pathfinder_geometry::vector::{vec2f, vec2i}; 20 | } 21 | 22 | #[cfg(feature = "png")] 23 | use std::fs::*; 24 | #[cfg(feature = "png")] 25 | use std::io::BufWriter; 26 | 27 | use crate::stroke::*; 28 | use crate::{IntRect, IntPoint, Point, Transform, Vector}; 29 | 30 | use euclid::vec2; 31 | 32 | #[derive(Clone)] 33 | pub struct Mask { 34 | pub width: i32, 35 | pub height: i32, 36 | pub data: Vec, 37 | } 38 | 39 | /// A premultiplied color. i.e. r,b,g <= a 40 | #[derive(Clone, Copy, PartialEq, Debug)] 41 | pub struct SolidSource { 42 | pub r: u8, 43 | pub g: u8, 44 | pub b: u8, 45 | pub a: u8, 46 | } 47 | 48 | impl SolidSource { 49 | pub fn to_u32(&self) -> u32 { 50 | let color = ((self.a as u32) << 24) 51 | | ((self.r as u32) << 16) 52 | | ((self.g as u32) << 8) 53 | | ((self.b as u32) << 0); 54 | color 55 | } 56 | 57 | pub fn from_unpremultiplied_argb(a: u8, r: u8, g: u8, b: u8) -> Self { 58 | SolidSource { 59 | a: a, 60 | r: muldiv255(a as u32, r as u32) as u8, 61 | g: muldiv255(a as u32, g as u32) as u8, 62 | b: muldiv255(a as u32, b as u32) as u8 63 | } 64 | } 65 | } 66 | 67 | impl From for SolidSource { 68 | fn from(color: Color) -> Self { 69 | SolidSource::from_unpremultiplied_argb( 70 | color.a(), 71 | color.r(), 72 | color.g(), 73 | color.b(), 74 | ) 75 | } 76 | } 77 | 78 | #[derive(PartialEq, Clone, Copy, Debug)] 79 | pub enum BlendMode { 80 | Dst, 81 | Src, 82 | Clear, 83 | SrcOver, 84 | DstOver, 85 | SrcIn, 86 | DstIn, 87 | SrcOut, 88 | DstOut, 89 | SrcAtop, 90 | DstAtop, 91 | Xor, 92 | Add, 93 | 94 | Screen, 95 | Overlay, 96 | Darken, 97 | Lighten, 98 | ColorDodge, 99 | ColorBurn, 100 | HardLight, 101 | SoftLight, 102 | Difference, 103 | Exclusion, 104 | Multiply, 105 | Hue, 106 | Saturation, 107 | Color, 108 | Luminosity 109 | } 110 | 111 | trait Blender { 112 | type Output; 113 | fn build() -> Self::Output; 114 | } 115 | 116 | struct BlendRow; 117 | 118 | fn blend_row(src: &[u32], dst: &mut [u32]) { 119 | for (dst, src) in dst.iter_mut().zip(src) { 120 | *dst = T::blend(*src, *dst); 121 | } 122 | } 123 | 124 | impl Blender for BlendRow { 125 | type Output = fn(&[u32], &mut [u32]); 126 | fn build() -> Self::Output { 127 | blend_row:: 128 | } 129 | } 130 | 131 | struct BlendRowMask; 132 | 133 | fn blend_row_mask(src: &[u32], mask: &[u8], dst: &mut [u32]) { 134 | for ((dst, src), mask) in dst.iter_mut().zip(src).zip(mask) { 135 | *dst = lerp( 136 | *dst, 137 | T::blend(*src, *dst), 138 | alpha_to_alpha256(*mask as u32), 139 | ); 140 | } 141 | } 142 | 143 | impl Blender for BlendRowMask { 144 | type Output = fn(&[u32], &[u8], &mut [u32]); 145 | fn build() -> Self::Output { 146 | blend_row_mask:: 147 | } 148 | } 149 | 150 | struct BlendRowMaskClip; 151 | 152 | fn blend_row_mask_clip(src: &[u32], mask: &[u8], clip: &[u8], dst: &mut [u32]) { 153 | for (((dst, src), mask), clip) in dst.iter_mut().zip(src).zip(mask).zip(clip) { 154 | *dst = alpha_lerp( 155 | *dst, 156 | T::blend(*src, *dst), 157 | *mask as u32, 158 | *clip as u32 159 | ); 160 | } 161 | } 162 | 163 | impl Blender for BlendRowMaskClip { 164 | type Output = fn(&[u32], &[u8], &[u8], &mut [u32]); 165 | fn build() -> Self::Output { 166 | blend_row_mask_clip:: 167 | } 168 | } 169 | 170 | fn build_blend_proc(mode: BlendMode) -> T::Output { 171 | use sw_composite::blend::*; 172 | match mode { 173 | BlendMode::Dst => T::build::(), 174 | BlendMode::Src => T::build::(), 175 | BlendMode::Clear => T::build::(), 176 | BlendMode::SrcOver => T::build::(), 177 | BlendMode::DstOver => T::build::(), 178 | BlendMode::SrcIn => T::build::(), 179 | BlendMode::DstIn => T::build::(), 180 | BlendMode::SrcOut => T::build::(), 181 | BlendMode::DstOut => T::build::(), 182 | BlendMode::SrcAtop => T::build::(), 183 | BlendMode::DstAtop => T::build::(), 184 | BlendMode::Xor => T::build::(), 185 | BlendMode::Add => T::build::(), 186 | BlendMode::Screen => T::build::(), 187 | BlendMode::Overlay => T::build::(), 188 | BlendMode::Darken => T::build::(), 189 | BlendMode::Lighten => T::build::(), 190 | BlendMode::ColorDodge => T::build::(), 191 | BlendMode::ColorBurn => T::build::(), 192 | BlendMode::HardLight => T::build::(), 193 | BlendMode::SoftLight => T::build::(), 194 | BlendMode::Difference => T::build::(), 195 | BlendMode::Exclusion => T::build::(), 196 | BlendMode::Multiply => T::build::(), 197 | BlendMode::Hue => T::build::(), 198 | BlendMode::Saturation => T::build::(), 199 | BlendMode::Color => T::build::(), 200 | BlendMode::Luminosity => T::build::(), 201 | } 202 | } 203 | 204 | #[derive(Copy, Clone)] 205 | pub enum ExtendMode { 206 | Pad, 207 | Repeat 208 | } 209 | 210 | #[derive(Copy, Clone, PartialEq)] 211 | pub enum FilterMode { 212 | Bilinear, 213 | Nearest 214 | } 215 | 216 | /// LinearGradients have an implicit start point at 0,0 and an end point at 256,0. The transform 217 | /// parameter can be used to adjust them to the desired location. 218 | /// RadialGradients have an implicit center at 0,0 and a radius of 128. 219 | /// The helper functions: `new_linear_gradient`, `new_radial_gradient` and `new_two_circle_radial_gradient` 220 | /// allow the gradients to be constructed with easier to understand inputs. 221 | /// The `transform` parameter maps user space to source space. This means that setting the same transform 222 | /// on the draw target as the source will have the effect of canceling out. 223 | /// 224 | /// These locations are an artifact of the blitter implementation and will probably change in the 225 | /// future to become more ergonomic. 226 | #[derive(Clone)] 227 | pub enum Source<'a> { 228 | Solid(SolidSource), 229 | Image(Image<'a>, ExtendMode, FilterMode, Transform), 230 | RadialGradient(Gradient, Spread, Transform), 231 | TwoCircleRadialGradient(Gradient, Spread, Point, f32, Point, f32, Transform), 232 | LinearGradient(Gradient, Spread, Transform), 233 | SweepGradient(Gradient, Spread, f32, f32, Transform), 234 | } 235 | 236 | impl From for Source<'_> { 237 | fn from(other: SolidSource) -> Self { 238 | Source::Solid(other) 239 | } 240 | } 241 | 242 | impl From for Source<'_> { 243 | fn from(color: Color) -> Self { 244 | Source::Solid(SolidSource::from(color)) 245 | } 246 | } 247 | 248 | impl<'a> Source<'a> { 249 | /// Creates a new linear gradient source where the start point corresponds to the gradient 250 | /// stop at position = 0 and the end point corresponds to the gradient stop at position = 1. 251 | pub fn new_linear_gradient(gradient: Gradient, start: Point, end: Point, spread: Spread) -> Source<'a> { 252 | let gradient_vector = Vector::new(end.x - start.x, end.y - start.y); 253 | // Get length of desired gradient vector 254 | let length = gradient_vector.length(); 255 | if length != 0. { 256 | let gradient_vector = gradient_vector.normalize(); 257 | 258 | let sin = gradient_vector.y; 259 | let cos = gradient_vector.x; 260 | // Build up a rotation matrix from our vector 261 | let mat = Transform::new(cos, -sin, sin, cos, 0., 0.); 262 | 263 | // Adjust for the start point 264 | let mat = mat.pre_translate(vec2(-start.x, -start.y)); 265 | 266 | // Scale gradient to desired length 267 | let mat = mat.then_scale(1. / length, 1. / length); 268 | Source::LinearGradient(gradient, spread, mat) 269 | } else { 270 | // use some degenerate matrix 271 | Source::LinearGradient(gradient, spread, Transform::scale(0., 0.)) 272 | } 273 | } 274 | 275 | /// Creates a new radial gradient that is centered at the given point and has the given radius. 276 | pub fn new_radial_gradient(gradient: Gradient, center: Point, radius: f32, spread: Spread) -> Source<'a> { 277 | // Scale gradient to desired radius 278 | let scale = Transform::scale(radius, radius); 279 | // Transform gradient to center of gradient 280 | let translate = Transform::translation(center.x, center.y); 281 | // Compute final transform 282 | let transform = scale.then(&translate).inverse().unwrap(); 283 | 284 | Source::RadialGradient(gradient, spread, transform) 285 | } 286 | 287 | /// Creates a new radial gradient that is centered at the given point and has the given radius. 288 | pub fn new_two_circle_radial_gradient(gradient: Gradient, center1: Point, radius1: f32, center2: Point, radius2: f32, spread: Spread) -> Source<'a> { 289 | let transform = Transform::identity(); 290 | Source::TwoCircleRadialGradient(gradient, spread, center1, radius1, center2, radius2, transform) 291 | } 292 | 293 | /// Creates a new sweep gradient that is centered at the given point with `start_angle` and `end_angle`. 294 | pub fn new_sweep_gradient(gradient: Gradient, center: Point, start_angle: f32, end_angle: f32, spread: Spread) -> Source<'a> { 295 | // Transform gradient to center of gradient 296 | let transform = Transform::translation(-center.x, -center.y); 297 | Source::SweepGradient(gradient, spread, start_angle, end_angle, transform) 298 | } 299 | } 300 | 301 | #[derive(PartialEq, Clone, Copy, Debug)] 302 | pub enum AntialiasMode { 303 | None, 304 | Gray, 305 | } 306 | 307 | #[derive(PartialEq, Clone, Copy, Debug)] 308 | pub struct DrawOptions { 309 | pub blend_mode: BlendMode, 310 | pub alpha: f32, 311 | pub antialias: AntialiasMode, 312 | } 313 | 314 | impl DrawOptions { 315 | pub fn new() -> Self { 316 | Default::default() 317 | } 318 | } 319 | 320 | impl Default for DrawOptions { 321 | fn default() -> Self { 322 | DrawOptions { 323 | blend_mode: BlendMode::SrcOver, 324 | alpha: 1., 325 | antialias: AntialiasMode::Gray, 326 | } 327 | } 328 | } 329 | 330 | #[derive(Clone)] 331 | struct Clip { 332 | rect: IntRect, 333 | mask: Option>, 334 | } 335 | 336 | #[derive(Clone)] 337 | struct Layer { 338 | buf: Vec, 339 | opacity: f32, 340 | rect: IntRect, 341 | blend: BlendMode, 342 | } 343 | 344 | fn scaled_tolerance(x: f32, trans: &Transform) -> f32 { 345 | // The absolute value of the determinant is the area parallelogram 346 | // Take the sqrt of the area to losily convert to one dimension 347 | x / trans.determinant().abs().sqrt() 348 | } 349 | 350 | 351 | 352 | /// The main type used for drawing 353 | pub struct DrawTarget> { 354 | width: i32, 355 | height: i32, 356 | rasterizer: Rasterizer, 357 | current_point: Option, 358 | first_point: Option, 359 | buf: Backing, 360 | clip_stack: Vec, 361 | layer_stack: Vec, 362 | transform: Transform, 363 | } 364 | 365 | impl DrawTarget { 366 | pub fn new(width: i32, height: i32) -> DrawTarget { 367 | DrawTarget { 368 | width, 369 | height, 370 | current_point: None, 371 | first_point: None, 372 | rasterizer: Rasterizer::new(width, height), 373 | buf: vec![0; (width * height) as usize], 374 | clip_stack: Vec::new(), 375 | layer_stack: Vec::new(), 376 | transform: Transform::identity(), 377 | } 378 | } 379 | 380 | /// Use a previously used vector for the bitmap and extend it to the given size(if needed) 381 | pub fn from_vec(width: i32, height: i32, mut vec: Vec) -> DrawTarget{ 382 | vec.resize((width*height) as usize, 0); 383 | DrawTarget { 384 | width, 385 | height, 386 | current_point: None, 387 | first_point: None, 388 | rasterizer: Rasterizer::new(width, height), 389 | buf: vec, 390 | clip_stack: Vec::new(), 391 | layer_stack: Vec::new(), 392 | transform: Transform::identity() 393 | } 394 | } 395 | 396 | /// Take ownership of the buffer backing the DrawTarget 397 | pub fn into_vec(self) -> Vec { 398 | self.buf 399 | } 400 | } 401 | 402 | impl + AsMut<[u32]>> DrawTarget { 403 | /// Use an existing backing storage for the bitmap 404 | /// 405 | /// The backing store must be the correct size (width*height elements). 406 | pub fn from_backing(width: i32, height: i32, buf : Backing) -> Self { 407 | assert_eq!((width*height) as usize, buf.as_ref().len()); 408 | DrawTarget { 409 | width, 410 | height, 411 | current_point: None, 412 | first_point: None, 413 | rasterizer: Rasterizer::new(width, height), 414 | buf, 415 | clip_stack: Vec::new(), 416 | layer_stack: Vec::new(), 417 | transform: Transform::identity() 418 | } 419 | } 420 | 421 | pub fn width(&self) -> i32 { 422 | self.width 423 | } 424 | 425 | pub fn height(&self) -> i32 { 426 | self.height 427 | } 428 | 429 | /// sets a transform that will be applied to all drawing operations 430 | pub fn set_transform(&mut self, transform: &Transform) { 431 | self.transform = *transform; 432 | } 433 | 434 | /// gets the current transform 435 | pub fn get_transform(&self) -> &Transform { 436 | &self.transform 437 | } 438 | 439 | fn move_to(&mut self, pt: Point) { 440 | self.current_point = Some(pt); 441 | self.first_point = Some(pt); 442 | } 443 | 444 | fn line_to(&mut self, pt: Point) { 445 | if self.current_point.is_none() { 446 | self.current_point = Some(pt); 447 | self.first_point = Some(pt); 448 | } 449 | if let Some(current_point) = self.current_point { 450 | self.rasterizer 451 | .add_edge(current_point, pt, false, Point::new(0., 0.)); 452 | self.current_point = Some(pt); 453 | } 454 | } 455 | 456 | fn quad_to(&mut self, cpt: Point, pt: Point) { 457 | if self.current_point.is_none() { 458 | self.current_point = Some(cpt); 459 | self.first_point = Some(cpt); 460 | } 461 | if let Some(current_point) = self.current_point { 462 | let curve = [current_point, cpt, pt]; 463 | self.current_point = Some(curve[2]); 464 | self.add_quad(curve); 465 | } 466 | } 467 | 468 | fn add_quad(&mut self, mut curve: [Point; 3]) { 469 | let a = curve[0].y; 470 | let b = curve[1].y; 471 | let c = curve[2].y; 472 | if is_not_monotonic(a, b, c) { 473 | let mut t_value = 0.; 474 | if valid_unit_divide(a - b, a - b - b + c, &mut t_value) { 475 | let mut dst = [Point::new(0., 0.); 5]; 476 | chop_quad_at(&curve, &mut dst, t_value); 477 | flatten_double_quad_extrema(&mut dst); 478 | self.rasterizer.add_edge(dst[0], dst[2], true, dst[1]); 479 | self.rasterizer.add_edge(dst[2], dst[4], true, dst[3]); 480 | return; 481 | } 482 | // if we get here, we need to force dst to be monotonic, even though 483 | // we couldn't compute a unit_divide value (probably underflow). 484 | let b = if (a - b).abs() < (b - c).abs() { a } else { c }; 485 | curve[1].y = b; 486 | } 487 | self.rasterizer.add_edge(curve[0], curve[2], true, curve[1]); 488 | } 489 | 490 | fn cubic_to(&mut self, cpt1: Point, cpt2: Point, pt: Point) { 491 | if self.current_point.is_none() { 492 | self.current_point = Some(cpt1); 493 | self.first_point = Some(cpt1); 494 | } 495 | if let Some(current_point) = self.current_point { 496 | let c = CubicBezierSegment { 497 | from: current_point, 498 | ctrl1: cpt1, 499 | ctrl2: cpt2, 500 | to: pt, 501 | }; 502 | c.for_each_quadratic_bezier(0.01, &mut |q| { 503 | let curve = [q.from, q.ctrl, q.to]; 504 | self.add_quad(curve); 505 | }); 506 | self.current_point = Some(pt); 507 | } 508 | } 509 | 510 | fn close(&mut self) { 511 | if let (Some(first_point), Some(current_point)) = (self.first_point, self.current_point) { 512 | self.rasterizer.add_edge( 513 | current_point, 514 | first_point, 515 | false, 516 | Point::new(0., 0.), 517 | ); 518 | } 519 | self.current_point = self.first_point; 520 | } 521 | 522 | fn apply_path(&mut self, path: &Path) { 523 | 524 | // we have no height so there can be no edges 525 | if self.height == 0 { 526 | return; 527 | } 528 | 529 | for op in &path.ops { 530 | match *op { 531 | PathOp::MoveTo(pt) => { 532 | self.close(); 533 | self.move_to(self.transform.transform_point(pt)); 534 | }, 535 | PathOp::LineTo(pt) => self.line_to(self.transform.transform_point(pt)), 536 | PathOp::QuadTo(cpt, pt) => self.quad_to( 537 | self.transform.transform_point(cpt), 538 | self.transform.transform_point(pt), 539 | ), 540 | PathOp::CubicTo(cpt1, cpt2, pt) => self.cubic_to( 541 | self.transform.transform_point(cpt1), 542 | self.transform.transform_point(cpt2), 543 | self.transform.transform_point(pt), 544 | ), 545 | PathOp::Close => self.close(), 546 | } 547 | } 548 | // make sure the path is closed 549 | self.close(); 550 | // XXX: we'd like for this function to return the bounds of the path 551 | } 552 | 553 | pub fn push_clip_rect(&mut self, rect: IntRect) { 554 | // intersect with current clip 555 | let clip = match self.clip_stack.last() { 556 | Some(Clip { 557 | rect: current_clip, 558 | mask: _, 559 | }) => Clip { 560 | rect: current_clip.intersection_unchecked(&rect), 561 | mask: None, 562 | }, 563 | _ => Clip { 564 | rect: rect, 565 | mask: None, 566 | }, 567 | }; 568 | self.clip_stack.push(clip); 569 | } 570 | 571 | pub fn pop_clip(&mut self) { 572 | self.clip_stack.pop(); 573 | } 574 | 575 | pub fn push_clip(&mut self, path: &Path) { 576 | self.apply_path(path); 577 | 578 | // XXX: restrict to clipped area 579 | let mut blitter = MaskSuperBlitter::new(0, 0, self.width, self.height); 580 | self.rasterizer.rasterize(&mut blitter, path.winding); 581 | 582 | if let Some(last) = self.clip_stack.last() { 583 | // combine with previous mask 584 | if let Some(last_mask) = &last.mask { 585 | for i in 0..((self.width * self.height) as usize) { 586 | blitter.buf[i] = muldiv255(blitter.buf[i] as u32, last_mask[i] as u32) as u8 587 | } 588 | } 589 | } 590 | 591 | let current_bounds = self.clip_bounds(); 592 | //XXX: handle interleaving of clip rect/masks better 593 | self.clip_stack.push(Clip { 594 | rect: current_bounds, 595 | mask: Some(blitter.buf), 596 | }); 597 | self.rasterizer.reset(); 598 | } 599 | 600 | fn clip_bounds(&self) -> IntRect { 601 | self.clip_stack.last().map(|c| c.rect).unwrap_or(IntRect::new( 602 | euclid::Point2D::new(0, 0), 603 | euclid::Point2D::new(self.width, self.height), 604 | )) 605 | } 606 | 607 | /// Pushes a new layer as the drawing target. This is used for implementing 608 | /// group opacity effects. 609 | pub fn push_layer(&mut self, opacity: f32) { 610 | self.push_layer_with_blend(opacity, BlendMode::SrcOver) 611 | } 612 | 613 | /// Pushes a new layer as the drawing target. This is used for implementing 614 | /// group opacity or blend effects. 615 | pub fn push_layer_with_blend(&mut self, opacity: f32, blend: BlendMode) { 616 | let rect = self.clip_bounds(); 617 | self.layer_stack.push(Layer { 618 | rect, 619 | buf: vec![0; (rect.size().width * rect.size().height) as usize], 620 | opacity, 621 | blend 622 | }); 623 | } 624 | 625 | /// Draws the most recently pushed layer to the drawing target with 626 | /// the pushed opacity applied. 627 | pub fn pop_layer(&mut self) { 628 | let layer = self.layer_stack.pop().unwrap(); 629 | let opacity = (layer.opacity * 255. + 0.5) as u8; 630 | // Allocating an entire mask just for the opacity is needlessly bad. 631 | // We should be able to fix it once the blitters work better. 632 | let mask = vec![opacity; (self.width * self.height) as usize]; 633 | let size = layer.rect.size(); 634 | let ctm = self.transform; 635 | self.transform = Transform::identity(); 636 | let image = Source::Image(Image { 637 | width: size.width, 638 | height: size.height, 639 | data: &layer.buf 640 | }, 641 | ExtendMode::Pad, 642 | FilterMode::Nearest, 643 | Transform::translation(-layer.rect.min.x as f32, 644 | -layer.rect.min.y as f32)); 645 | self.composite(&image, Some(&mask), intrect(0, 0, self.width, self.height), layer.rect, layer.blend, 1.); 646 | self.transform = ctm; 647 | } 648 | 649 | /// Draws an image at (x, y) with the size (width, height). This will rescale the image to the 650 | /// destination size. 651 | pub fn draw_image_with_size_at(&mut self, width: f32, height: f32, x: f32, y: f32, image: &Image, options: &DrawOptions) { 652 | let source = Source::Image(*image, 653 | ExtendMode::Pad, 654 | FilterMode::Bilinear, 655 | Transform::translation(-x, -y).then_scale(image.width as f32 / width, image.height as f32 / height)); 656 | 657 | self.fill_rect(x, y, width, height, &source, options); 658 | } 659 | 660 | /// Draws an image at x, y 661 | pub fn draw_image_at(&mut self, x: f32, y: f32, image: &Image, options: &DrawOptions) { 662 | self.draw_image_with_size_at(image.width as f32, image.height as f32, x, y, image, options); 663 | } 664 | 665 | /// Draws `src` through an untransformed `mask` positioned at `x`, `y` in device space 666 | pub fn mask(&mut self, src: &Source, x: i32, y: i32, mask: &Mask) { 667 | self.composite(src, Some(&mask.data), intrect(x, y, mask.width, mask.height), intrect(x, y, mask.width, mask.height), BlendMode::SrcOver, 1.); 668 | } 669 | 670 | /// Strokes `path` with `style` and fills the result with `src` 671 | pub fn stroke(&mut self, path: &Path, src: &Source, style: &StrokeStyle, options: &DrawOptions) { 672 | let tolerance = 0.1; 673 | 674 | // Since we're flattening in userspace, we need to compensate for the transform otherwise 675 | // we'll flatten too much or not enough depending on the scale. We approximate doing this 676 | // correctly by scaling the tolerance value using the same mechanism as Fitz. This 677 | // approximation will fail if the scale between axes is drastically different. An 678 | // alternative would be to use transform specific flattening but I haven't seen that done 679 | // anywhere. 680 | let tolerance = scaled_tolerance(tolerance, &self.transform); 681 | let mut path = path.flatten(tolerance); 682 | 683 | if !style.dash_array.is_empty() { 684 | path = dash_path(&path, &style.dash_array, style.dash_offset); 685 | } 686 | let stroked = stroke_to_path(&path, style); 687 | self.fill(&stroked, src, options); 688 | } 689 | 690 | /// Fills the rect `x`, `y,`, `width`, `height` with `src`. If the result is an 691 | /// integer aligned rectangle performance will be faster than filling a rectangular path. 692 | pub fn fill_rect(&mut self, x: f32, y: f32, width: f32, height: f32, src: &Source, options: &DrawOptions) { 693 | let ix = x as i32; 694 | let iy = y as i32; 695 | let iwidth = width as i32; 696 | let iheight = height as i32; 697 | let integer_rect = ix as f32 == x && iy as f32 == y && 698 | iwidth as f32 == width && iheight as f32 == height; 699 | 700 | if self.transform == Transform::identity() && integer_rect && self.clip_stack.is_empty() { 701 | let bounds = intrect(0, 0, self.width, self.height); 702 | let mut irect = intrect(ix, iy, ix + iwidth, iy + iheight); 703 | irect = match irect.intersection(&bounds) { 704 | Some(irect) => irect, 705 | _ => return, 706 | }; 707 | self.composite(src, None, irect, irect, options.blend_mode, options.alpha); 708 | } else { 709 | let mut pb = PathBuilder::new(); 710 | pb.rect(x, y, width, height); 711 | self.fill(&pb.finish(), src, options); 712 | } 713 | } 714 | 715 | /// Fills `path` with `src` 716 | pub fn fill(&mut self, path: &Path, src: &Source, options: &DrawOptions) { 717 | self.apply_path(path); 718 | let bounds = self.rasterizer.get_bounds(); 719 | if bounds.size().width > 0 && bounds.size().height > 0 { 720 | match options.antialias { 721 | AntialiasMode::None => { 722 | let mut blitter = MaskBlitter::new(bounds.min.x, bounds.min.y, bounds.size().width, bounds.size().height); 723 | self.rasterizer.rasterize(&mut blitter, path.winding); 724 | self.composite( 725 | src, 726 | Some(&blitter.buf), 727 | bounds, 728 | bounds, 729 | options.blend_mode, 730 | options.alpha, 731 | ); 732 | } 733 | AntialiasMode::Gray => { 734 | let mut blitter = MaskSuperBlitter::new(bounds.min.x, bounds.min.y, bounds.size().width, bounds.size().height); 735 | self.rasterizer.rasterize(&mut blitter, path.winding); 736 | self.composite( 737 | src, 738 | Some(&blitter.buf), 739 | bounds, 740 | bounds, 741 | options.blend_mode, 742 | options.alpha, 743 | ); 744 | } 745 | } 746 | } 747 | self.rasterizer.reset(); 748 | } 749 | 750 | /// Fills the current clip with the solid color `solid` 751 | pub fn clear(&mut self, solid: SolidSource) { 752 | let mut pb = PathBuilder::new(); 753 | if self.clip_stack.is_empty() { 754 | let color = solid.to_u32(); 755 | for pixel in self.buf.as_mut() { 756 | *pixel = color; 757 | } 758 | } else { 759 | let ctm = self.transform; 760 | self.transform = Transform::identity(); 761 | pb.rect(0., 0., self.width as f32, self.height as f32); 762 | self.fill( 763 | &pb.finish(), 764 | &Source::Solid(solid), 765 | &DrawOptions { 766 | blend_mode: BlendMode::Src, 767 | alpha: 1., 768 | antialias: AntialiasMode::Gray, 769 | }, 770 | ); 771 | self.transform = ctm; 772 | } 773 | } 774 | 775 | #[cfg(feature = "text")] 776 | pub fn draw_text( 777 | &mut self, 778 | font: &fk::Font, 779 | point_size: f32, 780 | text: &str, 781 | start: Point, 782 | src: &Source, 783 | options: &DrawOptions, 784 | ) { 785 | let mut start = fk::vec2f(start.x, start.y); 786 | let mut ids = Vec::new(); 787 | let mut positions = Vec::new(); 788 | for c in text.chars() { 789 | let id = font.glyph_for_char(c).unwrap(); 790 | ids.push(id); 791 | positions.push(Point::new(start.x(), start.y())); 792 | start += font.advance(id).unwrap() * point_size / 24. / 96.; 793 | } 794 | self.draw_glyphs(font, point_size, &ids, &positions, src, options); 795 | } 796 | 797 | #[cfg(feature = "text")] 798 | pub fn draw_glyphs( 799 | &mut self, 800 | font: &fk::Font, 801 | point_size: f32, 802 | ids: &[u32], 803 | positions: &[Point], 804 | src: &Source, 805 | options: &DrawOptions, 806 | ) { 807 | let antialias_mode = match options.antialias { 808 | AntialiasMode::Gray => fk::RasterizationOptions::GrayscaleAa, 809 | AntialiasMode::None => fk::RasterizationOptions::Bilevel, 810 | }; 811 | let mut combined_bounds = euclid::Rect::zero(); 812 | for (id, position) in ids.iter().zip(positions.iter()) { 813 | let bounds = font.raster_bounds( 814 | *id, 815 | point_size, 816 | fk::Transform2F::row_major(self.transform.m11, self.transform.m12, self.transform.m21, self.transform.m22, 0., 0.) 817 | .translate(fk::vec2f(position.x, position.y)), 818 | fk::HintingOptions::None, 819 | antialias_mode, 820 | ); 821 | combined_bounds = match bounds { 822 | Ok(bounds) => { 823 | let origin = bounds.origin(); 824 | let size = bounds.size(); 825 | let bounds = euclid::Rect::new(IntPoint::new(origin.x(), origin.y()), euclid::Size2D::new(size.x(), size.y())); 826 | combined_bounds.union(&bounds) 827 | } 828 | _ => panic!(), 829 | } 830 | } 831 | 832 | /*let mut canvas = Canvas::new(&euclid::Size2D::new(combined_bounds.size.width as u32, 833 | combined_bounds.size.height as u32), Format::A8);*/ 834 | let mut canvas = fk::Canvas::new( 835 | fk::vec2i(combined_bounds.size.width, combined_bounds.size.height), 836 | fk::Format::A8, 837 | ); 838 | for (id, position) in ids.iter().zip(positions.iter()) { 839 | let mut position = self.transform.transform_point(*position); 840 | position.x -= combined_bounds.origin.x as f32; 841 | position.y -= combined_bounds.origin.y as f32; 842 | font.rasterize_glyph( 843 | &mut canvas, 844 | *id, 845 | point_size, 846 | fk::Transform2F::row_major(self.transform.m11, self.transform.m12, self.transform.m21, self.transform.m22, 0., 0.) 847 | .translate(fk::vec2f(position.x, position.y)), 848 | fk::HintingOptions::None, 849 | antialias_mode, 850 | ).unwrap(); 851 | } 852 | 853 | self.composite( 854 | src, 855 | Some(&canvas.pixels), 856 | combined_bounds.to_box2d(), 857 | combined_bounds.to_box2d(), 858 | options.blend_mode, 859 | 1., 860 | ); 861 | } 862 | } 863 | 864 | impl DrawTarget { 865 | fn choose_blitter<'a, 'b, 'c>(mask: Option<&[u8]>, clip_stack: &'a Vec, blitter_storage: &'b mut ShaderBlitterStorage<'a>, shader: &'a dyn Shader, blend: BlendMode, dest: &'a mut [u32], dest_bounds: IntRect, width: i32) -> &'b mut dyn Blitter { 866 | *blitter_storage = match (mask, clip_stack.last()) { 867 | (Some(_mask), Some(Clip { 868 | rect: _, 869 | mask: Some(clip), 870 | })) => { 871 | if blend == BlendMode::SrcOver { 872 | let scb = ShaderClipMaskBlitter { 873 | x: dest_bounds.min.x, 874 | y: dest_bounds.min.y, 875 | shader, 876 | tmp: vec![0; width as usize], 877 | dest, 878 | dest_stride: dest_bounds.size().width, 879 | clip, 880 | clip_stride: width, 881 | }; 882 | ShaderBlitterStorage::ShaderClipMaskBlitter(scb) 883 | } else { 884 | let blend_fn = build_blend_proc::(blend); 885 | let scb_blend = ShaderClipBlendMaskBlitter { 886 | x: dest_bounds.min.x, 887 | y: dest_bounds.min.y, 888 | shader, 889 | tmp: vec![0; width as usize], 890 | dest, 891 | dest_stride: dest_bounds.size().width, 892 | clip, 893 | clip_stride: width, 894 | blend_fn 895 | }; 896 | ShaderBlitterStorage::ShaderClipBlendMaskBlitter(scb_blend) 897 | } 898 | } 899 | (Some(_mask), _) => { 900 | if blend == BlendMode::SrcOver { 901 | let sb = ShaderMaskBlitter { 902 | x: dest_bounds.min.x, 903 | y: dest_bounds.min.y, 904 | shader: &*shader, 905 | tmp: vec![0; width as usize], 906 | dest, 907 | dest_stride: dest_bounds.size().width, 908 | }; 909 | ShaderBlitterStorage::ShaderMaskBlitter(sb) 910 | } else { 911 | let blend_fn = build_blend_proc::(blend); 912 | let sb_blend = ShaderBlendMaskBlitter { 913 | x: dest_bounds.min.x, 914 | y: dest_bounds.min.y, 915 | shader: &*shader, 916 | tmp: vec![0; width as usize], 917 | dest, 918 | dest_stride: dest_bounds.size().width, 919 | blend_fn, 920 | }; 921 | ShaderBlitterStorage::ShaderBlendMaskBlitter(sb_blend) 922 | } 923 | } 924 | (None, _) => { 925 | let blend_fn = build_blend_proc::(blend); 926 | let sb_blend = ShaderBlendBlitter { 927 | x: dest_bounds.min.x, 928 | y: dest_bounds.min.y, 929 | shader: &*shader, 930 | tmp: vec![0; width as usize], 931 | dest, 932 | dest_stride: dest_bounds.size().width, 933 | blend_fn, 934 | }; 935 | ShaderBlitterStorage::ShaderBlendBlitter(sb_blend) 936 | } 937 | }; 938 | 939 | match blitter_storage { 940 | ShaderBlitterStorage::None => unreachable!(), 941 | ShaderBlitterStorage::ShaderBlendMaskBlitter(s) => s, 942 | ShaderBlitterStorage::ShaderClipBlendMaskBlitter(s) => s, 943 | ShaderBlitterStorage::ShaderMaskBlitter(s) => s, 944 | ShaderBlitterStorage::ShaderClipMaskBlitter(s) => s, 945 | ShaderBlitterStorage::ShaderBlendBlitter(s) => s, 946 | } 947 | } 948 | } 949 | 950 | impl + AsMut<[u32]>> DrawTarget { 951 | /// `mask_rect` is in DrawTarget space. i.e size is the size of the mask and origin is the position. 952 | /// you can not render a part of the mask 953 | fn composite(&mut self, src: &Source, mask: Option<&[u8]>, mask_rect: IntRect, mut rect: IntRect, blend: BlendMode, alpha: f32) { 954 | let ti = self.transform.inverse(); 955 | let ti = if let Some(ti) = ti { 956 | ti 957 | } else { 958 | // the transform is not invertible so we have nothing to draw 959 | return; 960 | }; 961 | 962 | let clip_bounds = self.clip_bounds(); 963 | 964 | let (dest, dest_bounds) = match self.layer_stack.last_mut() { 965 | Some(layer) => (&mut layer.buf[..], layer.rect), 966 | None => (self.buf.as_mut(), intrect(0, 0, self.width, self.height)) 967 | }; 968 | 969 | rect = rect 970 | .intersection_unchecked(&clip_bounds) 971 | .intersection_unchecked(&dest_bounds) 972 | .intersection_unchecked(&mask_rect); 973 | if rect.is_empty() { 974 | return; 975 | } 976 | 977 | let mut shader_storage = ShaderStorage::None; 978 | let shader = choose_shader(&ti, src, alpha, &mut shader_storage); 979 | 980 | let mut blitter_storage = ShaderBlitterStorage::None; 981 | let blitter = DrawTarget::choose_blitter(mask, &self.clip_stack, &mut blitter_storage, shader, blend, dest, dest_bounds, self.width); 982 | 983 | match mask { 984 | Some(mask) => { 985 | for y in rect.min.y..rect.max.y { 986 | let mask_row = (y - mask_rect.min.y) * mask_rect.size().width; 987 | let mask_start = (mask_row + rect.min.x - mask_rect.min.x) as usize; 988 | let mask_end = (mask_row + rect.max.x - mask_rect.min.x) as usize; 989 | blitter.blit_span(y, rect.min.x, rect.max.x, &mask[mask_start..mask_end]); 990 | } 991 | } 992 | None => { 993 | for y in rect.min.y..rect.max.y { 994 | let empty_mask = []; 995 | blitter.blit_span(y, rect.min.x, rect.max.x, &empty_mask[..]); 996 | } 997 | } 998 | }; 999 | } 1000 | 1001 | /// Draws `src_rect` of `src` at `dst`. The current transform and clip are ignored 1002 | pub fn composite_surface>(&mut self, src: &DrawTarget, src_rect: IntRect, dst: IntPoint, f: F) { 1003 | let dst_rect = intrect(0, 0, self.width, self.height); 1004 | 1005 | // intersect the src_rect with the source size so that we don't go out of bounds 1006 | let src_rect = src_rect.intersection_unchecked(&intrect(0, 0, src.width, src.height)); 1007 | 1008 | let src_rect = dst_rect 1009 | .intersection_unchecked(&src_rect.translate(dst.to_vector())).translate(-dst.to_vector()); 1010 | 1011 | // clamp requires Float so open code it 1012 | let dst = IntPoint::new(dst.x.max(dst_rect.min.x).min(dst_rect.max.x), 1013 | dst.y.max(dst_rect.min.y).min(dst_rect.max.y)); 1014 | 1015 | if src_rect.is_empty() { 1016 | return; 1017 | } 1018 | 1019 | for y in src_rect.min.y..src_rect.max.y { 1020 | let dst_row_start = (dst.x + (dst.y + y - src_rect.min.y) * self.width) as usize; 1021 | let dst_row_end = dst_row_start + src_rect.size().width as usize; 1022 | let src_row_start = (src_rect.min.x + y * src.width) as usize; 1023 | let src_row_end = src_row_start + src_rect.size().width as usize; 1024 | f(&src.buf.as_ref()[src_row_start..src_row_end], &mut self.buf.as_mut()[dst_row_start..dst_row_end]); 1025 | } 1026 | } 1027 | 1028 | /// Draws `src_rect` of `src` at `dst`. The current transform and clip are ignored. 1029 | /// `src_rect` is clamped to (0, 0, src.width, src.height). 1030 | pub fn copy_surface>(&mut self, src: &DrawTarget, src_rect: IntRect, dst: IntPoint) { 1031 | self.composite_surface(src, src_rect, dst, |src, dst| { 1032 | dst.copy_from_slice(src) 1033 | }) 1034 | } 1035 | 1036 | /// Blends `src_rect` of `src` at `dst`using `blend` mode. 1037 | /// The current transform and clip are ignored. 1038 | /// `src_rect` is clamped to (0, 0, `src.width`, `src.height`). 1039 | pub fn blend_surface>(&mut self, src: &DrawTarget, src_rect: IntRect, dst: IntPoint, blend: BlendMode) { 1040 | let blend_fn = build_blend_proc::(blend); 1041 | self.composite_surface(src, src_rect, dst, |src, dst| { 1042 | blend_fn(src, dst); 1043 | }); 1044 | } 1045 | 1046 | /// Blends `src_rect` of `src` at `dst` using `alpha`. The current transform and clip are ignored. 1047 | /// `src_rect` is clamped to (0, 0, `src.width`, `src.height`). 1048 | pub fn blend_surface_with_alpha>(&mut self, src: &DrawTarget, src_rect: IntRect, dst: IntPoint, alpha: f32) { 1049 | let alpha = (alpha * 255. + 0.5) as u8; 1050 | 1051 | self.composite_surface(src, src_rect, dst, |src, dst| { 1052 | over_in_row(src, dst, alpha as u32); 1053 | }); 1054 | } 1055 | 1056 | /// Returns a reference to the underlying pixel data 1057 | pub fn get_data(&self) -> &[u32] { 1058 | self.buf.as_ref() 1059 | } 1060 | 1061 | /// Returns a mut reference to the underlying pixel data as ARGB with a representation 1062 | /// like: (A << 24) | (R << 16) | (G << 8) | B 1063 | pub fn get_data_mut(&mut self) -> &mut [u32] { 1064 | self.buf.as_mut() 1065 | } 1066 | 1067 | /// Returns a reference to the underlying pixel data as individual bytes with the order BGRA 1068 | /// on little endian. 1069 | pub fn get_data_u8(&self) -> &[u8] { 1070 | let buf = self.buf.as_ref(); 1071 | let p = buf.as_ptr(); 1072 | let len = buf.len(); 1073 | // we want to return an [u8] slice instead of a [u32] slice. This is a safe thing to 1074 | // do because requirements of a [u32] slice are stricter. 1075 | unsafe { std::slice::from_raw_parts(p as *const u8, len * std::mem::size_of::()) } 1076 | } 1077 | 1078 | /// Returns a mut reference to the underlying pixel data as individual bytes with the order BGRA 1079 | /// on little endian. 1080 | pub fn get_data_u8_mut(&mut self) -> &mut [u8] { 1081 | let buf = self.buf.as_mut(); 1082 | let p = buf.as_mut_ptr(); 1083 | let len = buf.len(); 1084 | // we want to return an [u8] slice instead of a [u32] slice. This is a safe thing to 1085 | // do because requirements of a [u32] slice are stricter. 1086 | unsafe { std::slice::from_raw_parts_mut(p as *mut u8, len * std::mem::size_of::()) } 1087 | } 1088 | 1089 | /// Take ownership of the buffer backing the DrawTarget 1090 | pub fn into_inner(self) -> Backing { 1091 | self.buf 1092 | } 1093 | 1094 | /// Saves the current pixels to a png file at `path` 1095 | #[cfg(feature = "png")] 1096 | pub fn write_png>(&self, path: P) -> Result<(), png::EncodingError> { 1097 | let file = File::create(path)?; 1098 | 1099 | let w = &mut BufWriter::new(file); 1100 | self.write_png_to_writer(w) 1101 | } 1102 | 1103 | /// Saves the current pixels to a png and writes it to `w` 1104 | #[cfg(feature = "png")] 1105 | pub fn write_png_to_writer(&self, w: Write) -> Result<(), png::EncodingError> { 1106 | let mut encoder = png::Encoder::new(w, self.width as u32, self.height as u32); 1107 | encoder.set_color(png::ColorType::Rgba); 1108 | encoder.set_depth(png::BitDepth::Eight); 1109 | let mut writer = encoder.write_header()?; 1110 | let buf = self.buf.as_ref(); 1111 | let mut output = Vec::with_capacity(buf.len() * 4); 1112 | 1113 | for pixel in buf { 1114 | let a = (pixel >> 24) & 0xffu32; 1115 | let mut r = (pixel >> 16) & 0xffu32; 1116 | let mut g = (pixel >> 8) & 0xffu32; 1117 | let mut b = (pixel >> 0) & 0xffu32; 1118 | 1119 | if a > 0u32 { 1120 | r = r * 255u32 / a; 1121 | g = g * 255u32 / a; 1122 | b = b * 255u32 / a; 1123 | } 1124 | 1125 | output.push(r as u8); 1126 | output.push(g as u8); 1127 | output.push(b as u8); 1128 | output.push(a as u8); 1129 | } 1130 | 1131 | writer.write_image_data(&output) 1132 | } 1133 | } 1134 | --------------------------------------------------------------------------------