| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use { |
| anyhow::Error, |
| argh::FromArgs, |
| carnelian::{ |
| color::Color, |
| drawing::path_for_rectangle, |
| input::{self}, |
| make_app_assistant, |
| render::{ |
| BlendMode, Composition, Context, CopyRegion, Fill, FillRule, Image, Layer, PostCopy, |
| PreClear, Raster, RenderExt, Style, |
| }, |
| App, AppAssistant, RenderOptions, Size, ViewAssistant, ViewAssistantContext, |
| ViewAssistantPtr, ViewKey, |
| }, |
| euclid::{ |
| default::{Point2D, Rect, Vector2D}, |
| point2, size2, |
| }, |
| fuchsia_trace_provider, |
| fuchsia_zircon::{AsHandleRef, Event, Signals}, |
| std::{collections::BTreeMap, fs::File}, |
| }; |
| |
| const WHITE_COLOR: Color = Color { r: 255, g: 255, b: 255, a: 255 }; |
| |
| /// Png. |
| #[derive(Debug, FromArgs)] |
| #[argh(name = "png-rs")] |
| struct Args { |
| /// use spinel (GPU rendering back-end) |
| #[argh(switch, short = 's')] |
| use_spinel: bool, |
| |
| /// PNG file to load (default is lenna.png) |
| #[argh(option, default = "String::from(\"lenna.png\")")] |
| file: String, |
| |
| /// background color (default is white) |
| #[argh(option, from_str_fn(parse_color))] |
| background: Option<Color>, |
| |
| /// an optional x,y position for the image (default is center) |
| #[argh(option, from_str_fn(parse_point))] |
| position: Option<Point2D<f32>>, |
| } |
| |
| fn parse_color(value: &str) -> Result<Color, String> { |
| Color::from_hash_code(value).map_err(|err| err.to_string()) |
| } |
| |
| fn parse_point(value: &str) -> Result<Point2D<f32>, String> { |
| let mut coords = vec![]; |
| for value in value.split(",") { |
| coords.push(value.parse::<f32>().map_err(|err| err.to_string())?); |
| } |
| if coords.len() == 2 { |
| Ok(point2(coords[0], coords[1])) |
| } else { |
| Err("bad position".to_string()) |
| } |
| } |
| |
| #[derive(Default)] |
| struct PngAppAssistant { |
| use_spinel: bool, |
| filename: String, |
| background: Option<Color>, |
| position: Option<Point2D<f32>>, |
| } |
| |
| impl AppAssistant for PngAppAssistant { |
| fn setup(&mut self) -> Result<(), Error> { |
| let args: Args = argh::from_env(); |
| self.use_spinel = args.use_spinel; |
| self.filename = args.file; |
| self.background = args.background; |
| self.position = args.position; |
| Ok(()) |
| } |
| |
| fn create_view_assistant(&mut self, _: ViewKey) -> Result<ViewAssistantPtr, Error> { |
| Ok(Box::new(PngViewAssistant::new( |
| self.filename.clone(), |
| self.background.take().unwrap_or(WHITE_COLOR), |
| self.position.take(), |
| ))) |
| } |
| |
| fn get_render_options(&self) -> RenderOptions { |
| RenderOptions { use_spinel: self.use_spinel, ..RenderOptions::default() } |
| } |
| } |
| |
| struct Rendering { |
| size: Size, |
| composition: Composition, |
| clear_raster: Option<Raster>, |
| } |
| |
| impl Rendering { |
| fn new(background: Color) -> Rendering { |
| let composition = Composition::new(background); |
| |
| Rendering { size: Size::zero(), composition, clear_raster: None } |
| } |
| } |
| |
| struct PngViewAssistant { |
| filename: String, |
| background: Color, |
| renderings: BTreeMap<u64, Rendering>, |
| png: Option<(Size, Image, Raster)>, |
| composition: Composition, |
| position: Option<Point2D<f32>>, |
| touch_locations: BTreeMap<input::pointer::PointerId, Point2D<f32>>, |
| } |
| |
| impl PngViewAssistant { |
| pub fn new(filename: String, background: Color, position: Option<Point2D<f32>>) -> Self { |
| let background = Color { r: background.r, g: background.g, b: background.b, a: 255 }; |
| let composition = Composition::new(background); |
| |
| Self { |
| filename, |
| background, |
| renderings: BTreeMap::new(), |
| png: None, |
| composition, |
| position, |
| touch_locations: BTreeMap::new(), |
| } |
| } |
| } |
| |
| impl ViewAssistant for PngViewAssistant { |
| fn setup(&mut self, _context: &ViewAssistantContext) -> Result<(), Error> { |
| Ok(()) |
| } |
| |
| fn render( |
| &mut self, |
| render_context: &mut Context, |
| ready_event: Event, |
| context: &ViewAssistantContext, |
| ) -> Result<(), Error> { |
| let background = self.background; |
| let image_id = context.image_id; |
| let rendering = |
| self.renderings.entry(image_id).or_insert_with(|| Rendering::new(background)); |
| let ext = if rendering.size != context.size { |
| rendering.size = context.size; |
| RenderExt { pre_clear: Some(PreClear { color: background }), ..Default::default() } |
| } else { |
| RenderExt::default() |
| }; |
| |
| // Create image from PNG and raster for clearing. |
| let filename = &self.filename; |
| let (png_size, png_image, png_raster) = self.png.take().unwrap_or_else(|| { |
| let file = File::open(format!("/pkg/data/static/{}", filename)) |
| .expect(&format!("failed to open file /pkg/data/static/{}", filename)); |
| let decoder = png::Decoder::new(file); |
| let (info, mut reader) = decoder.read_info().unwrap(); |
| let image = render_context |
| .new_image_from_png(&mut reader) |
| .expect(&format!("failed to decode file /pkg/data/static/{}", filename)); |
| let size = size2(info.width as f32, info.height as f32); |
| let mut raster_builder = render_context.raster_builder().expect("raster_builder"); |
| raster_builder |
| .add(&path_for_rectangle(&Rect::new(Point2D::zero(), size), render_context), None); |
| (size, image, raster_builder.build()) |
| }); |
| |
| // Center image if position has not been specified. |
| let position = self.position.take().unwrap_or_else(|| { |
| let x = (rendering.size.width - png_size.width) / 2.0; |
| let y = (rendering.size.height - png_size.height) / 2.0; |
| point2(x, y) |
| }); |
| |
| // Clear area where image was previously located. |
| let clear_layer = rendering.clear_raster.iter().map(|raster| Layer { |
| raster: raster.clone(), |
| style: Style { |
| fill_rule: FillRule::WholeTile, |
| fill: Fill::Solid(background), |
| blend_mode: BlendMode::Over, |
| }, |
| }); |
| rendering.composition.replace(.., clear_layer); |
| let image = render_context.get_current_image(context); |
| render_context.render(&rendering.composition, None, image, &ext); |
| |
| // Save clear raster for next frame. |
| let translation = position.to_vector().to_i32(); |
| let clear_raster = png_raster.clone().translate(translation); |
| rendering.clear_raster.replace(clear_raster); |
| |
| // Determine visible rect and copy |png_image| to |image|. |
| let dst_rect = Rect::new(position.to_i32(), png_size.to_i32()); |
| let output_rect = Rect::new(Point2D::zero(), rendering.size.to_i32()); |
| let ext = RenderExt { |
| post_copy: dst_rect.intersection(&output_rect).map(|visible_rect| PostCopy { |
| image, |
| color: background, |
| exposure_distance: Vector2D::zero(), |
| copy_region: CopyRegion { |
| src_offset: (visible_rect.origin - dst_rect.origin).to_point().to_u32(), |
| dst_offset: visible_rect.origin.to_u32(), |
| extent: visible_rect.size.to_u32(), |
| }, |
| }), |
| ..Default::default() |
| }; |
| // Empty clip to skip rendering and only copy image. |
| render_context.render(&self.composition, Some(Rect::zero()), png_image, &ext); |
| |
| self.png.replace((png_size, png_image, png_raster)); |
| self.position.replace(position); |
| |
| ready_event.as_handle_ref().signal(Signals::NONE, Signals::EVENT_SIGNALED)?; |
| |
| context.request_render(); |
| |
| Ok(()) |
| } |
| |
| fn handle_pointer_event( |
| &mut self, |
| _context: &mut ViewAssistantContext, |
| _event: &input::Event, |
| pointer_event: &input::pointer::Event, |
| ) -> Result<(), Error> { |
| match &pointer_event.phase { |
| input::pointer::Phase::Down(touch_location) => { |
| self.touch_locations |
| .insert(pointer_event.pointer_id.clone(), touch_location.to_f32()); |
| } |
| input::pointer::Phase::Moved(touch_location) => { |
| if let Some(location) = self.touch_locations.get_mut(&pointer_event.pointer_id) { |
| let mut position = self.position.take().unwrap_or_else(|| Point2D::zero()); |
| // Pan image using the change to touch location. |
| position += touch_location.to_f32() - *location; |
| self.position.replace(position); |
| *location = touch_location.to_f32(); |
| } |
| } |
| input::pointer::Phase::Up => { |
| self.touch_locations.remove(&pointer_event.pointer_id.clone()); |
| } |
| _ => (), |
| } |
| Ok(()) |
| } |
| } |
| |
| fn main() -> Result<(), Error> { |
| fuchsia_trace_provider::trace_provider_create_with_fdio(); |
| |
| println!("Png Example"); |
| App::run(make_app_assistant::<PngAppAssistant>()) |
| } |