From f8886d7c1f2d52f7e5cc3037d2b9513461417899 Mon Sep 17 00:00:00 2001 From: MeiHui FAN Date: Thu, 28 Sep 2023 11:46:45 +0800 Subject: [PATCH] support to compile ftgrays/ftraster/gpac-evg preparing for new renderer implementation --- .vscode/settings.json | 5 + Cargo.toml | 18 ++++ build.rs | 71 ++++++++++++- src/gpac_evg.rs | 242 ++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 3 + src/render_evg.rs | 211 ++++++++++++++++++++++++++++++++++++ tests/raster.rs | 67 ++++++++++++ 7 files changed, 613 insertions(+), 4 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 src/gpac_evg.rs create mode 100644 src/render_evg.rs create mode 100644 tests/raster.rs diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..3ef992a --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "C_Cpp.default.defines": [ "STANDALONE_", + "FALL_THROUGH=((void)0)", "FT_BEGIN_HEADER" ], + "C_Cpp.default.includePath": [ "src/evg", "src/raster" ] +} diff --git a/Cargo.toml b/Cargo.toml index fe4e828..5d56575 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,12 +22,30 @@ usvg = "0.36" kurbo = "0.10" # Bezier curves utils #build-time = { version = "0.1", git = "https://github.com/AlephAlpha/build-time" } +png = { version = "0.17", optional = true } + +[lib] +# Disable doctests as a workaround for https://github.com/rust-lang/rust-bindgen/issues/1313 +#doctest = false + +[features] +#default = [ "cc" ] +cc = [ "dep:cc", "dep:bindgen", "dep:glob", "dep:png" ] # implied by optional dependency [dev-dependencies] #rexpect = "0.5" [build-dependencies] +cc = { version = "1.0", optional = true } +bindgen = { version = "0.68", optional = true } +glob = { version = "0.3", optional = true } chrono = "0.4" +[profile.release] +codegen-units = 1 +strip = 'debuginfo' +panic = 'abort' +lto = 'fat' # true + [workspace] diff --git a/build.rs b/build.rs index 8ad21b6..364ce24 100644 --- a/build.rs +++ b/build.rs @@ -1,16 +1,79 @@ -fn main() { // https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html +// https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html +fn main() -> Result<(), Box> { + //println!("cargo:rerun-if-changed=build.rs"); // XXX: prevent re-run indead // By default, cargo always re-run the build script if any file within the package // is changed, and no any rerun-if instruction is emitted. println!("cargo:rerun-if-changed=src/"); println!("cargo:rerun-if-changed=.git/index"); let output = std::process::Command::new("git") - .args(["rev-parse", "--short", "HEAD"]).output().unwrap(); - println!("cargo:rustc-env=BUILD_GIT_HASH={}", String::from_utf8(output.stdout).unwrap()); + .args(["rev-parse", "--short", "HEAD"]).output()?; + println!("cargo:rustc-env=BUILD_GIT_HASH={}", String::from_utf8(output.stdout)?); println!("cargo:rustc-env=BUILD_TIMESTAMP={}", chrono::Local::now().format("%H:%M:%S%z %Y-%m-%d")); - //println!("cargo:rerun-if-changed=build.rs"); // XXX: prevent re-run indead + #[cfg(feature = "cc")] { use std::path::PathBuf; + #[derive(Debug)] struct DoctestComment; + impl bindgen::callbacks::ParseCallbacks for DoctestComment { + fn process_comment(&self, comment: &str) -> Option { + Some(format!("````c,ignore\n{comment}\n````")) // FIXME: + } + } + + let module = "ftgrays"; // "ftgrays_bindings"; + cc::Build::new().flag("-std=c17").flag("-pedantic") + .define("FALL_THROUGH", "((void)0)").file("src/raster/ftgrays.c") + .opt_level(3).define("NDEBUG", None).file("src/raster/ftraster.c") + .files(glob::glob("src/raster/stroke/*.c")?.filter_map(Result::ok)) + .flag("-Wno-unused-variable")//.flag("-Wno-unused-function") + .define("STANDALONE_", None).compile(module); + + // The bindgen::Builder is the main entry point to bindgen, + // and lets you build up options for the resulting bindings. + bindgen::Builder::default() //.header("src/raster/ftimage.h") + .clang_arg("-DSTANDALONE_").header("src/raster/ftgrays.h") + .clang_arg("-DFT_BEGIN_HEADER=").clang_arg("-DFT_END_HEADER=") + .clang_arg("-DFT_STATIC_BYTE_CAST(type,var)=(type)(unsigned char)(var)") + .allowlist_item("FT_OUTLINE_.*|FT_RASTER_FLAG_.*|FT_CURVE_TAG.*") + .layout_tests(false).derive_copy(false).allowlist_var("ft_standard_raster") + .allowlist_var("ft_grays_raster").allowlist_type("FT_Outline|FT_Pixel_Mode") + .default_enum_style(bindgen::EnumVariation::Rust { non_exhaustive: true }) + .default_macro_constant_type(bindgen::MacroTypeVariation::Signed) + .parse_callbacks(Box::new(DoctestComment)).generate_comments(false) // XXX: + // Tell cargo to invalidate the built crate whenever any of the + // included header files changed. + .parse_callbacks(Box::new(bindgen::CargoCallbacks)).generate()? + .write_to_file(PathBuf::from(std::env::var("OUT_DIR")?).join(format!("{module}.rs")))?; + + /* ln -s /path/to/freetype2/{include/freetype/ftimage.h,src/smooth/ftgrays.[ch],\ + src/raster/ft{misc.h,raster.c}} src/raster/ + * ln -s /path/to/freetype2/src/{raster/ftmisc.h,base/ft{stroke,trigon}.c} src/raster/stroke/ + * ln -s /path/to/freetype2/include/freetype/ft{stroke,trigon,image}.h src/raster/stroke/ + * ln -s /path/to/gpac/src/evg/{ftgrays.c,rast_soft.h,stencil.c,surface.c,\ + raster_{argb,rgb,565,yuv}.c},raster3d.c src/evg/ + * ln -s /path/to/gpac/src/utils/{path2d{,_stroke},math,alloc,color,error}.c src/evg/ + * ln -s /path/gpac/include/gpac/{evg,setup,constants,maths,color,path2d,\ + tools,thread}.h src/evg/gpac/ + * touch src/evg/gpac/{Remotery,config_file,configuration,main,module,version}.h + */ + let module = "gpac_evg"; // "evg_bindings"; + cc::Build::new().flag("-std=c17").flag("-Isrc/evg").define("GPAC_FIXED_POINT", None) + .flag("-Wno-unused-parameter").define("GPAC_DISABLE_THREADS", None) + .flag("-Wno-pointer-sign").define("GPAC_DISABLE_LOG", None) + .files(glob::glob("src/evg/*.c")?.filter_map(Result::ok)) + .opt_level(3).define("NDEBUG", None).compile(module); + + bindgen::Builder::default().header("src/evg/gpac/evg.h").clang_arg("-Isrc/evg") + .default_enum_style(bindgen::EnumVariation::Rust { non_exhaustive: true }) + .clang_arg("-DGPAC_DISABLE_THREADS").allowlist_function("gf_evg_s.*") + //.allowlist_item("GF_LINE_.*") + .clang_arg("-DGPAC_FIXED_POINT").allowlist_function("gf_path_.*") + .layout_tests(false).derive_copy(false).new_type_alias("Fixed") + .parse_callbacks(Box::new(bindgen::CargoCallbacks)).generate()? + .write_to_file(PathBuf::from(std::env::var("OUT_DIR")?).join(format!("{module}.rs")))?; + } + + Ok(()) } diff --git a/src/gpac_evg.rs b/src/gpac_evg.rs new file mode 100644 index 0000000..0cbc037 --- /dev/null +++ b/src/gpac_evg.rs @@ -0,0 +1,242 @@ +/**************************************************************** + * $ID: gpac_evg.rs Tue 24 Oct 2023 15:58:07+0800 * + * * + * Maintainer: 范美辉 (MeiHui FAN) * + * Copyright (c) 2023 M.H.Fan, All rights reserved. * + ****************************************************************/ + +#![allow(non_snake_case)] #![allow(non_camel_case_types)] #![allow(non_upper_case_globals)] +#![allow(clippy::approx_constant)] #![allow(clippy::useless_transmute)] + +//#[macro_export] macro_rules! dbg_ne { ($($v:expr),*) => () } +#[macro_export] macro_rules! dbg_ne { ($v:expr, $g:expr) => { if $v != $g { dbg!($v); } } } + +/* TODO: https://github.com/sammycage/plutovg + https://github.com/kiba/SDL_svg/blob/master/ftgrays.c + https://github.com/Samsung/rlottie/tree/master/src/vector/freetype + https://gitlab.freedesktop.org/freetype/freetype/-/blob/master/src/smooth/ftgrays.c + https://gitlab.freedesktop.org/freetype/freetype/-/blob/master/src/raster/ftraster.c */ + +pub mod ftgrays { include!(concat!(env!("OUT_DIR"), "/ftgrays.rs")); + impl From<(FT_Pos, FT_Pos)> for FT_Vector { + fn from(v: (FT_Pos, FT_Pos)) -> Self { Self { x: v.0, y: v.1 } } + } +} + +//pub mod gpac_evg { // https://github.com/gpac/gpac/tree/master/src/evg/ +include!(concat!(env!("OUT_DIR"), "/gpac_evg.rs")); // evg_bindings.rs + +impl From for Fixed { #[inline] fn from(v: i32) -> Self { Self(v << 16) } } +impl From for i32 { #[inline] fn from(v: Fixed) -> Self { (v.0 + (1 << 15)) >> 16 } } +impl From for Fixed { // 16.16 fixed-point, or 26.6? + #[inline] fn from(v: f32) -> Self { Self((v * (1 << 16) as f32) as i32) } +} +impl From for f32 { #[inline] fn from(v: Fixed) -> Self { v.0 as f32 / (1 << 16) as f32 } } + +impl From for Bool { + fn from(value: bool) -> Self { if value { Bool::GF_TRUE } else { Bool::GF_FALSE } } +} + +impl Copy for Fixed {} +impl Copy for GF_Point2D {} +impl Copy for GF_PenSettings {} +impl Clone for Fixed { #[inline] fn clone(&self) -> Self { *self } } +impl Clone for GF_Point2D { #[inline] fn clone(&self) -> Self { *self } } +impl Clone for GF_PenSettings { #[inline] fn clone(&self) -> Self { *self } } + +impl Default for GF_PenSettings { + fn default() -> Self { Self { + width: 0.into(), cap: 1, join: 1, align: 0, dash: 0, + // GF_LINE_(CAP/JOIN)_ROUND, GF_PATH_LINE_CENTER, GF_DASH_STYLE_PLAIN + dash_offset: 0.into(), dash_set: core::ptr::null_mut(), + path_length: 0.into(), miterLimit: 0.into(), + } } +} + +pub struct VGPath(*mut GF_Path); +impl Drop for VGPath { fn drop(&mut self) { unsafe { gf_path_del(self.0) } } } +impl VGPath { #[allow(clippy::new_without_default)] // to build path and stencil + #[inline] pub fn new() -> Self { Self(unsafe { gf_path_new() }) } + + #[inline] pub fn move_to(&self, pt: GF_Point2D) { + let _err = unsafe { gf_path_add_move_to_vec(self.0, &pt as *const _ as *mut _) }; + dbg_ne!(_err, GF_Err::GF_OK); + } + + #[inline] pub fn line_to(&self, pt: GF_Point2D) { + let _err = unsafe { gf_path_add_line_to_vec(self.0, &pt as *const _ as *mut _) }; + dbg_ne!(_err, GF_Err::GF_OK); + } + + #[inline] pub fn cubic_to(&self, c1: GF_Point2D, c2: GF_Point2D, pt: GF_Point2D) { + let _err = unsafe { gf_path_add_cubic_to_vec(self.0, + &c1 as *const _ as *mut _, &c2 as *const _ as *mut _, &pt as *const _ as *mut _) + }; dbg_ne!(_err, GF_Err::GF_OK); + } + + #[inline] pub fn quad_to(&self, cp: GF_Point2D, pt: GF_Point2D) { + let _err = unsafe { gf_path_add_quadratic_to_vec(self.0, + &cp as *const _ as *mut _, &pt as *const _ as *mut _) + }; dbg_ne!(_err, GF_Err::GF_OK); + } + + #[inline] pub fn svg_arc_to(&self, radius: GF_Point2D, + rotation: Fixed, large: bool, sweep: bool, pt: GF_Point2D) { + let _err = unsafe { gf_path_add_svg_arc_to(self.0, pt.x, pt.y, + radius.x, radius.y, rotation, large.into(), sweep.into()) + }; dbg_ne!(_err, GF_Err::GF_OK); + } + + #[inline] pub fn add_rect(&self, rect: GF_Rect) { + let _err = unsafe { // the outline is not closed, missing the last segment + gf_path_add_rect(self.0, rect.x, rect.y, rect.width, rect.height) + }; dbg_ne!(_err, GF_Err::GF_OK); + + /* path.move_to(GF_Point2D { x: rect.x, y: rect.y }); + path.line_to(GF_Point2D { x: Fixed(rect.x.0 + rect.width.0), y: rect.y }); + path.line_to(GF_Point2D { x: Fixed(rect.x.0 + rect.width.0), + y: Fixed(rect.y.0 - rect.height.0) }); + path.line_to(GF_Point2D { x: rect.x, y: Fixed(rect.y.0 - rect.height.0) }); + path.line_to(GF_Point2D { x: rect.x, y: rect.y }); // XXX: + path.close(); */ + } + + //gf_path_add_arc_to(path, end_x, end_y, fa_x, fa_y, fb_x, fb_y, cw); + //gf_path_add_arc(path, radius, start_angle, end_angle, close_type); + //gf_path_add_ellipse(path, cx, cy, a_axis, b_axis); + //gf_path_add_bezier(path, pts, nb_pts); + + #[inline] pub fn reset(&self) { unsafe { gf_path_reset(self.0) }; } + + #[allow(clippy::len_without_is_empty)] #[inline] pub fn len(&self) -> u32 { + if let Some(path) = unsafe { self.0.as_ref() } { path.n_points } else { 0 } + } + pub fn last_point(&self) -> Option { + let cnt = self.len(); if cnt < 1 { None } else { + Some(unsafe { *(*self.0).points.offset(cnt as isize - 1) }) + } + } + + #[inline] pub fn close(&self) { + let _err = unsafe { gf_path_close(self.0) }; dbg_ne!(_err, GF_Err::GF_OK); + } +} + +pub struct Stencil(*mut GF_EVGStencil); +impl Drop for Stencil { fn drop(&mut self) { unsafe { gf_evg_stencil_delete(self.0) } } } +impl Stencil { + #[inline] pub fn new(t: GF_StencilType) -> Self { unsafe { Self(gf_evg_stencil_new(t)) } } + + #[inline] pub fn set_color(&self, color: GF_Color) { + let _err = unsafe { gf_evg_stencil_set_brush_color(self.0, color) }; + dbg_ne!(_err, GF_Err::GF_OK); + } + + #[inline] pub fn set_linear(&self, start: GF_Point2D, end: GF_Point2D) { + let _err = unsafe { + gf_evg_stencil_set_linear_gradient(self.0, start.x, start.y, end.x, end.y) + }; dbg_ne!(_err, GF_Err::GF_OK); + } + + #[inline] pub fn set_radial(&self, + center: GF_Point2D, focal: GF_Point2D, radius: GF_Point2D) { + let _err = unsafe { gf_evg_stencil_set_radial_gradient(self.0, + center.x, center.y, focal.x, focal.y, radius.x, radius.y) }; + dbg_ne!(_err, GF_Err::GF_OK); + } + + #[inline] pub fn push_interpolation(&self, pos: Fixed, col: GF_Color) { + let _err = unsafe { + gf_evg_stencil_push_gradient_interpolation(self.0, pos, col) + }; dbg_ne!(_err, GF_Err::GF_OK); + } + + /* #[inline] pub fn set_interpolation(&self, pos: &[Fixed], col: &[GF_Color], cnt: u32) { + let _err = unsafe { gf_evg_stencil_set_gradient_interpolation(self.0, + pos.as_mut_ptr(), col.as_mut_ptr(), cnt) }; dbg_ne!(_err, GF_Err::GF_OK); + } */ + + //let _err = gf_evg_stencil_set_gradient_mode(sten, GF_GradientMode::GF_GRADIENT_MODE_PAD); + //let _err = gf_evg_stencil_set_alpha(sten, alpha); + + #[inline] pub fn set_matrix(&self, mat: &GF_Matrix2D) { + let _err = unsafe { + gf_evg_stencil_set_matrix(self.0, mat as *const _ as *mut _) + }; dbg_ne!(_err, GF_Err::GF_OK); + } +} + +pub struct Surface(*mut GF_EVGSurface); +impl Drop for Surface { fn drop(&mut self) { unsafe { gf_evg_surface_delete(self.0) } } } +impl Surface { + #[inline] pub fn new(pixm: &mut Pixmap) -> Self { + let surf = unsafe { Self(gf_evg_surface_new(Bool::GF_FALSE)) }; + let _err = unsafe { gf_evg_surface_attach_to_buffer(surf.0, + pixm.data.as_mut_ptr(), pixm.width, pixm.height, 4, (pixm.width << 2) as i32, + GF_PixelFormat::GF_PIXEL_RGBA) + }; dbg_ne!(_err, GF_Err::GF_OK); surf + //let _err = unsafe { gf_evg_surface_clear(surf, &mut bbox, 0xFF000000) }; + } + + #[inline] pub fn fill_path(&self, path: &VGPath, sten: &Stencil) { + let _err = unsafe { gf_evg_surface_set_path(self.0, path.0) }; + dbg_ne!(_err, GF_Err::GF_OK); + let _err = unsafe { gf_evg_surface_fill(self.0, sten.0) }; + dbg_ne!(_err, GF_Err::GF_OK); + } + +/** ``` + use intvg::gpac_evg::*; + + let mut pixm = Pixmap::new(1024, 512); + let mut pens = GF_PenSettings::default(); + let sten = Stencil::new(GF_StencilType::GF_STENCIL_SOLID); + let (surf, path) = (Surface::new(&mut pixm), VGPath::new()); + + path.add_rect(GF_Rect { x: (pixm.width as i32 >> 2).into(), + y: (pixm.height as i32 - (pixm.height as i32 >> 2)).into(), + width: (pixm.width as i32 >> 1).into(), height: (pixm.height as i32 >> 1).into() }); + sten.set_color(0xFF0000FF); surf.fill_path(&path, &sten); + + sten.set_color(0xAA00FF00); pens.width = 10.into(); + surf.stroke_path(&path, &sten, &pens); + + pixm.save_png(concat!("target", "/image.png")).unwrap(); //env!("OUT_DIR") +``` */ + + #[inline] pub fn stroke_path(&self, path: &VGPath, sten: &Stencil, pens: &GF_PenSettings) { + let path = VGPath(unsafe { gf_path_get_outline(path.0, *pens) }); // XXX: + self.fill_path(&path, sten); + } + + #[inline] pub fn set_matrix(&self, mat: &GF_Matrix2D) { + let _err = unsafe { + gf_evg_surface_set_matrix(self.0, mat as *const _ as *mut _) + }; dbg_ne!(_err, GF_Err::GF_OK); + } +} + +pub struct Pixmap { pub data: Vec, pub width: u32, pub height: u32, } + +impl Pixmap { + pub fn new(width: u32, height: u32) -> Self { + Self { width, height, data: vec![0; (width * height * 4) as usize] } + } + + pub fn save_png(&self, path: &str) -> Result<(), png::EncodingError> { + let bufw = std::io::BufWriter::new( + std::fs::File::create(std::path::Path::new(path))?); + let mut encoder = png::Encoder::new(bufw, self.width, self.height); + encoder.set_color(png::ColorType::Rgba); + encoder.set_depth(png::BitDepth::Eight); + + encoder.set_source_gamma(png::ScaledFloat::new(1.0 / 2.2)); + // png::ScaledFloat::from_scaled(45455) // 1.0 / 2.2 scaled by 100000 + //let source_chromaticities = png::SourceChromaticities::new( // unscaled instant + // (0.3127, 0.3290), (0.6400, 0.3300), (0.3000, 0.6000), (0.1500, 0.0600)); + //encoder.set_source_chromaticities(source_chromaticities); + encoder.write_header()?.write_image_data(&self.data)?; Ok(()) + } +} +//} + diff --git a/src/lib.rs b/src/lib.rs index 7e19ac2..246f369 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,3 +3,6 @@ pub mod tinyvg; pub mod render; pub mod convert; +#[cfg(feature = "cc")] pub mod gpac_evg; +#[cfg(feature = "cc")] pub mod render_evg; + diff --git a/src/render_evg.rs b/src/render_evg.rs new file mode 100644 index 0000000..79eff05 --- /dev/null +++ b/src/render_evg.rs @@ -0,0 +1,211 @@ + +use crate::tinyvg::*; +use crate::gpac_evg::{VGPath, Stencil, Surface, Pixmap}; +use std::result::Result; + +/* TODO: https://github.com/sammycage/plutovg + https://github.com/gpac/gpac/tree/master/src/evg/ + https://github.com/kiba/SDL_svg/blob/master/ftgrays.c + https://github.com/Samsung/rlottie/tree/master/src/vector/freetype + https://gitlab.freedesktop.org/freetype/freetype/-/blob/master/src/smooth/ftgrays.c + https://gitlab.freedesktop.org/freetype/freetype/-/blob/master/src/raster/ftraster.c */ + +pub trait Render { fn render(&self, scale: f32) -> Result; } + +impl Render for TVGImage { + fn render(&self, scale: f32) -> Result { + let mut pixm = Pixmap::new( + (self.header.width as f32 * scale).ceil() as u32, + (self.header.height as f32 * scale).ceil() as u32); + + // XXX: rendering up-scale and then scale down for anti-aliasing? + let (surf, path) = (Surface::new(&mut pixm), VGPath::new()); + let mut pens = GF_PenSettings::default(); + + let ts = GF_Matrix2D { m: [scale.into(), 0.into(), 0.into(), + 0.into(), scale.into(), 0.into()] }; + surf.set_matrix(&ts); + + for cmd in &self.commands { + match cmd { Command::EndOfDocument => (), + Command::FillPolyg(FillCMD { fill, coll }) => { + let mut iter = coll.iter(); + if let Some(pt) = iter.next() { path.move_to((*pt).into()) } + iter.for_each(|pt| path.line_to((*pt).into())); path.close(); + surf.fill_path(&path, &style_to_stencil(self, fill, &ts)?); + } + Command::FillRects(FillCMD { fill, coll }) => { + let sten = style_to_stencil(self, fill, &ts)?; + coll.iter().for_each(|rect| { path.add_rect(rect.into()); + surf.fill_path(&path, &sten); path.reset(); + }); + } + Command::FillPath (FillCMD { fill, coll }) => { + let sten = style_to_stencil(self, fill, &ts)?; + for seg in coll { let _ = segment_to_path(seg, &path); + //if res { return Err("Got line width in fill path segment") } + surf.fill_path(&path, &sten); path.reset(); + } + } + Command::DrawLines(DrawCMD { line, lwidth, coll }) => { + coll.iter().for_each(|line| { path.move_to(line.start.into()); + path.line_to(line. end.into()); }); + pens.width = (*lwidth).into(); + surf.stroke_path(&path, &style_to_stencil(self, line, &ts)?, &pens); + } + Command::DrawLoop (DrawCMD { line, lwidth, coll }, + strip) => { let mut iter = coll.iter(); + if let Some(pt) = iter.next() { path.move_to((*pt).into()) } + iter.for_each(|pt| path.line_to((*pt).into())); + if !*strip { path.close(); } + + pens.width = (*lwidth).into(); + surf.stroke_path(&path, &style_to_stencil(self, line, &ts)?, &pens); + } + Command::DrawPath (DrawCMD { + line, lwidth, coll }) => { + let sten = style_to_stencil(self, line, &ts)?; + pens.width = (*lwidth).into(); + + for seg in coll { + stroke_segment_path(seg, &surf, &sten, &mut pens); } + } + Command::OutlinePolyg(fill, DrawCMD { + line, lwidth, coll }) => { + let mut iter = coll.iter(); + if let Some(pt) = iter.next() { path.move_to((*pt).into()) } + iter.for_each(|pt| path.line_to((*pt).into())); path.close(); + + pens.width = (*lwidth).into(); + surf. fill_path(&path, &style_to_stencil(self, fill, &ts)?); + surf.stroke_path(&path, &style_to_stencil(self, line, &ts)?, &pens); + } + Command::OutlineRects(fill, DrawCMD { + line, lwidth, coll }) => { + let paint = style_to_stencil(self, fill, &ts)?; + let pline = style_to_stencil(self, line, &ts)?; + pens.width = (*lwidth).into(); + + coll.iter().for_each(|rect| { path.add_rect(rect.into()); + surf. fill_path(&path, &paint); + surf.stroke_path(&path, &pline, &pens); path.reset(); + }); + } + Command::OutlinePath (fill, DrawCMD { + line, lwidth, coll }) => { + let paint = style_to_stencil(self, fill, &ts)?; + let pline = style_to_stencil(self, line, &ts)?; + pens.width = (*lwidth).into(); + + for seg in coll { let res = segment_to_path(seg, &path); + surf.fill_path(&path, &paint); + + if res { stroke_segment_path(seg, &surf, &pline, &mut pens); + } else { surf.stroke_path(&path, &pline, &pens); } path.reset(); + } + } + } path.reset(); + } Ok(pixm) + } +} + +fn stroke_segment_path(seg: &Segment, surf: &Surface, + sten: &Stencil, pens: &mut GF_PenSettings) { + let path = VGPath::new(); + path.move_to(seg.start.into()); + + for cmd in &seg.cmds { + if let Some(width) = cmd.lwidth { + if 1 < path.len() { + let start = path.last_point().unwrap(); + surf.stroke_path(&path, sten, pens); + path.reset(); path.move_to(start); + } pens.width = width.into(); + } process_segcmd(&path, &cmd.instr); + } surf.stroke_path(&path, sten, pens); +} + +fn segment_to_path(seg: &Segment, path: &VGPath) -> bool { + path.move_to(seg.start.into()); + let mut change_lw = false; + + for cmd in &seg.cmds { + if cmd.lwidth.is_some() { change_lw = true } + process_segcmd(path, &cmd.instr); + } change_lw +} + +fn process_segcmd(path: &VGPath, cmd: &SegInstr) { + match cmd { SegInstr::ClosePath => path.close(), + SegInstr::Line { end } => path.line_to((*end).into()), + SegInstr::HLine { x } => + path.line_to(GF_Point2D { x: (*x).into(), y: path.last_point().unwrap().y }), + SegInstr::VLine { y } => + path.line_to(GF_Point2D { x: path.last_point().unwrap().x, y: (*y).into() }), + + SegInstr::CubicBezier { ctrl, end } => + path.cubic_to(ctrl.0.into(), ctrl.1.into(), (*end).into()), + SegInstr::ArcCircle { large, sweep, radius, target } => + path.svg_arc_to(GF_Point2D { x: (*radius).into(), y: (*radius).into()}, + 0.into(), *large, *sweep, (*target).into()), + + SegInstr::ArcEllipse { large, sweep, radius, + rotation, target } => path.svg_arc_to( + GF_Point2D { x: radius.0.into(), y: radius.1.into() }, + (*rotation).into(), *large, *sweep, (*target).into()), + + SegInstr::QuadBezier { ctrl, end } => + path.quad_to((*ctrl).into(), (*end).into()), + } +} + +fn style_to_stencil(img: &TVGImage, style: &Style, ts: &GF_Matrix2D) -> + Result { use crate::gpac_evg::GF_StencilType::*; + let sten = match style { + Style::FlatColor(idx) => { + let sten = Stencil::new(GF_STENCIL_SOLID); + sten.set_color(img.lookup_color(*idx).into()); sten + } + Style::LinearGradient { points, cindex } => { + let sten = Stencil::new(GF_STENCIL_LINEAR_GRADIENT); + sten.push_interpolation(0.into(), img.lookup_color(cindex.0).into()); + sten.push_interpolation(1.into(), img.lookup_color(cindex.1).into()); + sten.set_linear(points.0.into(), points.1.into()); sten + } + Style::RadialGradient { points, cindex } => { + let sten = Stencil::new(GF_STENCIL_RADIAL_GRADIENT); + let radius = GF_Point2D { + x: (points.1.x - points.0.x).abs().into(), + y: (points.1.y - points.0.y).abs().into() }; + sten.push_interpolation(0.into(), img.lookup_color(cindex.0).into()); + sten.push_interpolation(1.into(), img.lookup_color(cindex.1).into()); + sten.set_radial(points.0.into(), points.1.into(), radius); sten + } + }; sten.set_matrix(ts); Ok(sten) +} + +use crate::gpac_evg::{GF_Point2D, GF_Rect, GF_Color, GF_Matrix2D, GF_PenSettings}; + +impl From for GF_Point2D { + fn from(pt: Point) -> Self { Self { x: pt.x.into(), y: pt.y.into() } } +} +impl From<&Rect> for GF_Rect { + fn from(rect: &Rect) -> Self { + Self { x: rect.l.into(), y: rect.b.into(), // screen to world coordinates + width: (rect.r - rect.l).into(), height: (rect.b - rect.t).into(), } + } +} +impl From for GF_Color { + fn from(color: RGBA8888) -> Self { // convert to ARGB + (color.a as u32) << 24 | (color.r as u32) << 16 | (color.g as u32) << 8 | color.b as u32 + } +} + +/* impl From for GF_Matrix2D { + fn from(mv: tiny_skia::Transform) -> Self { + Self { m: [mv.sx.into(), mv.kx.into(), mv.tx.into(), + mv.sy.into(), mv.ky.into(), mv.ty.into()] } + // sx = m[0], kx = m[1], tx = m[2], sy = m[3], ky = m[4], ty = m[5] + } +} */ + diff --git a/tests/raster.rs b/tests/raster.rs new file mode 100644 index 0000000..0645f08 --- /dev/null +++ b/tests/raster.rs @@ -0,0 +1,67 @@ + +#[cfg(feature = "cc")] +#[test] fn raster_ftgrays() -> Result<(), Box> { + use core::{ptr::null_mut, ffi::c_void}; use intvg::gpac_evg::ftgrays::*; + + let mut points = vec![ // FTDemo_Icon @freetype2/demo/src/ftcommon.c + ( 4, 8).into(), ( 4,10).into(), ( 8,12).into(), ( 8,52).into(), ( 4,54).into(), + ( 4,56).into(), (60,56).into(), (60,44).into(), (58,44).into(), (56,52).into(), + (44,52).into(), (44,12).into(), (48,10).into(), (48, 8).into(), (32, 8).into(), + (32,10).into(), (36,12).into(), (36,52).into(), (16,52).into(), (16,36).into(), + (24,36).into(), (26,40).into(), (28,40).into(), (28,28).into(), (26,28).into(), + (24,32).into(), (16,32).into(), (16,12).into(), (20,10).into(), (20, 8).into(), ]; + let mut tags = vec![FT_CURVE_TAG_ON as i8; points.len()]; + let mut contours = vec![29]; + + let mut bitmap = FT_Bitmap { + rows: 0, width: 0, pitch: 0, buffer: null_mut(), // FIXME: + + pixel_mode: FT_Pixel_Mode::FT_PIXEL_MODE_LCD as u8, // FT_PIXEL_MODE_BGRA + // https://docs.rs/bindgen/latest/bindgen/struct.Builder.html#enums + // Not used currently, or only used with FT_PIXEL_MODE_GRAY + palette_mode: 0, palette: null_mut(), num_grays: 0, + }; + + #[allow(unused)] + unsafe extern "C" fn span_func(y: i32, count: i32, // TODO: solid/linear/radial/stroke + spans: *const FT_Span, user: *mut c_void) { // The gray span drawing callback. + let bitmap = user as *mut FT_Bitmap; assert!(0 < count); + + for span in core::slice::from_raw_parts(spans, count as usize) { + for x in span.x..span.len as i16 { + // TODO: handle pixel at (x, y) with span.coverage (alpha) + } + } + } + + let outline = FT_Outline { + n_points: points.len() as i16, points: points.as_mut_ptr(), tags: tags.as_mut_ptr(), + n_contours: contours.len() as i16, contours: contours.as_mut_ptr(), + flags: FT_OUTLINE_NONE, // XXX: FT_OUTLINE_IGNORE_DROPOUTS + }; assert!(tags.len() == points.len()); + + let mut params = FT_Raster_Params { + source: &outline as *const _ as *const _, + user: &mut bitmap as *mut _ as *mut _, gray_spans: Some(span_func), + + // ftgrays.c: `ft_grays_raster` are typoed as `ft_gray_raster` in some comments. + // ftimage.h: FT_Pos can be typed as `signed int` rather than `long`? // XXX: + clip_box: FT_BBox { xMin: 0, yMin: 0, xMax: 0, yMax: 0 }, // FT_RASTER_FLAG_CLIP + flags: FT_RASTER_FLAG_AA | FT_RASTER_FLAG_DIRECT, + black_spans: None, bit_test: None, bit_set: None, // Unused + target: null_mut(), // &bitmap, unused in direct mode + }; let mut raster = null_mut(); + + unsafe { // use of extern static (ft_grays_raster/ft_standard_raster) is unsafe + let _ = ft_grays_raster.raster_new.unwrap()(null_mut(), &mut raster); + //if res != 0 { panic!("raster_new failed: {}", res); } + //ft_grays_raster.raster_reset.unwrap()(raster, null_mut(), 0); + //ft_grays_raster.raster_set_mode.unwrap()(raster, 0, null_mut()); + + let res = ft_grays_raster.raster_render.unwrap()(raster, &mut params); + if res != 0 { panic!("raster_render failed: {res}"); } + //ft_grays_raster.raster_done.unwrap()(raster); + } + + Ok(()) +}