// SPDX-License-Identifier: LGPL-3.0-or-later OR MPL-2.0
// This file is a part of `piet-glow`.
//
// `piet-glow` is free software: you can redistribute it and/or modify it under the terms of
// either:
//
// * GNU Lesser General Public License as published by the Free Software Foundation, either
// version 3 of the License, or (at your option) any later version.
// * Mozilla Public License as published by the Mozilla Foundation, version 2.
//
// `piet-glow` is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Lesser General Public License or the Mozilla Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License and the Mozilla
// Public License along with `piet-glow`. If not, see .
//! An example with a basic usage of the library.
#[path = "util/setup_context.rs"]
mod util;
use piet::kurbo::{Affine, BezPath, Point, Rect, Vec2};
use piet::{
FontFamily, GradientStop, RenderContext as _, Text, TextAttribute, TextLayout,
TextLayoutBuilder,
};
use instant::{Duration, Instant};
const ORANGES: &[u8] = include_bytes!("assets/oranges.jpg");
fn main() -> Result<(), Box> {
util::init();
// A path representing a star.
let star = generate_five_pointed_star(Point::new(0.0, 0.0), 75.0, 150.0);
let mut tick = 0;
// Get the test image at $CRATE_ROOT/examples/assets/test-image.png
let image = image::load_from_memory(ORANGES)?.to_rgba8();
// Convert the image to a byte buffer.
let size = image.dimensions();
let image_data = image.into_raw();
// Cached resources.
let mut image = None;
let mut solid_red = None;
let mut outline = None;
let mut radial_gradient = None;
let mut last_second = Instant::now();
let mut num_frames = 0;
let mut current_fps = None;
util::with_renderer(move |render_context, width, height| {
// Clear the screen to a light blue.
render_context.clear(None, piet::Color::rgb8(0x87, 0xce, 0xeb));
let red_star = {
let rot = (tick % 360) as f64 / 180.0 * std::f64::consts::PI;
let transform = Affine::translate((200.0, 200.0)) * Affine::rotate(rot);
transform * (&star)
};
// Draw a solid red using the path.
let solid_red =
solid_red.get_or_insert_with(|| render_context.solid_brush(piet::Color::OLIVE));
render_context.fill(&red_star, solid_red);
// Draw a black outline using the path.
let outline = outline.get_or_insert_with(|| render_context.solid_brush(piet::Color::BLACK));
render_context.stroke(&red_star, outline, 5.0);
// Test the transform.
render_context
.with_save(|render_context| {
let rot = ((tick * 2) % 360) as f64 / 180.0 * std::f64::consts::PI;
let trans = Affine::translate((600.0, 200.0))
* Affine::rotate(rot)
* Affine::scale_non_uniform(0.75, 0.75);
let gradient = radial_gradient.get_or_insert_with(|| {
let grad = piet::FixedRadialGradient {
center: Point::new(0.0, 0.0),
origin_offset: Vec2::new(0.0, 0.0),
radius: 150.0,
stops: vec![
GradientStop {
pos: 0.0,
color: piet::Color::LIME,
},
GradientStop {
pos: 0.5,
color: piet::Color::MAROON,
},
GradientStop {
pos: 1.0,
color: piet::Color::NAVY,
},
],
};
render_context.gradient(grad).unwrap()
});
render_context.transform(trans);
render_context.fill(&star, gradient);
render_context.stroke(&star, outline, 5.0);
Ok(())
})
.unwrap();
// Create an image and draw it.
let image = image.get_or_insert_with(|| {
render_context
.make_image(
size.0 as _,
size.1 as _,
&image_data,
piet::ImageFormat::RgbaSeparate,
)
.unwrap()
});
let scale = |x: f64| (x + 1.0) * 50.0;
let posn_shift_x = scale(((tick as f64) / 25.0).cos());
let posn_shift_y = scale(((tick as f64) / 25.0).sin());
let posn_x = posn_shift_x + 350.0;
let posn_y = posn_shift_y + 350.0;
let size_shift_x = ((tick as f64) / 50.0).cos() * 25.0;
let size_shift_y = ((tick as f64) / 50.0).sin() * 25.0;
render_context.draw_image(
image,
Rect::new(
posn_x,
posn_y,
posn_x + 100.0 + size_shift_x,
posn_y + 100.0 + size_shift_y,
),
piet::InterpolationMode::Bilinear,
);
// Also draw a subregion of the image.
let out_rect = Rect::new(100.0, 400.0, 200.0, 500.0);
render_context.draw_image_area(
image,
Rect::new(
25.0 + posn_shift_x,
25.0 + posn_shift_y,
100.0 + posn_shift_x,
100.0 + posn_shift_y,
),
out_rect,
piet::InterpolationMode::Bilinear,
);
render_context.stroke(out_rect, outline, 3.0);
// Text isn't supported on WASM yet.
if cfg!(not(any(target_arch = "wasm32", target_arch = "wasm64"))) {
// Update the FPS counter, if necessary.
num_frames += 1;
let now = Instant::now();
if now - last_second >= Duration::from_secs(1) {
let fps_string = format!("Frames per Second: {num_frames}");
let fps_text = render_context
.text()
.new_text_layout(fps_string)
.font(FontFamily::SERIF, 24.0)
.text_color(piet::Color::rgb8(0x11, 0x22, 0x22))
.default_attribute(TextAttribute::Underline(true))
.build()
.unwrap();
current_fps = Some(fps_text);
last_second = now;
num_frames = 0;
}
// Draw the FPS counter.
if let Some(current_fps) = current_fps.as_ref() {
let size = current_fps.size();
let pt = (
width as f64 - size.width - 10.0,
height as f64 - size.height - 10.0,
);
if pt.0 > 0.0 || pt.1 > 0.0 {
render_context.draw_text(current_fps, pt);
}
}
}
// Panic on any errors.
render_context.finish().unwrap();
render_context.status().unwrap();
tick += 1;
})
}
fn generate_five_pointed_star(center: Point, inner_radius: f64, outer_radius: f64) -> BezPath {
let point_from_polar = |radius: f64, angle: f64| {
let x = center.x + radius * angle.cos();
let y = center.y + radius * angle.sin();
Point::new(x, y)
};
let one_fifth_circle = std::f64::consts::PI * 2.0 / 5.0;
let outer_points = (0..5).map(|i| point_from_polar(outer_radius, one_fifth_circle * i as f64));
let inner_points = (0..5).map(|i| {
point_from_polar(
inner_radius,
one_fifth_circle * i as f64 + one_fifth_circle / 2.0,
)
});
let mut points = outer_points.zip(inner_points).flat_map(|(a, b)| [a, b]);
// Set up the path.
let mut path = BezPath::new();
path.move_to(points.next().unwrap());
// Add the points to the path.
for point in points {
path.line_to(point);
}
// Close the path.
path.close_path();
path
}