ruffle/web/src/shape_utils.rs

558 lines
20 KiB
Rust

use ruffle_core::backend::render::swf::{self, CharacterId, Color, FillStyle, LineStyle, Shape};
use ruffle_core::matrix::Matrix;
use std::collections::{HashMap, HashSet, VecDeque};
use svg::node::element::{
path::Data, Definitions, Image, LinearGradient, Path as SvgPath, Pattern, RadialGradient, Stop,
};
use svg::Document;
pub fn swf_shape_to_svg(shape: &Shape, bitmaps: &HashMap<CharacterId, (&str, u32, u32)>) -> String {
// Some browsers will vomit if you try to load/draw an image with 0 width/height.
// TODO(Herschel): Might be better to just return None in this case and skip
// rendering altogether.
let (width, height) = (
f32::max(shape.shape_bounds.x_max - shape.shape_bounds.x_min, 1.0),
f32::max(shape.shape_bounds.y_max - shape.shape_bounds.y_min, 1.0),
);
let mut document = Document::new()
.set("width", width)
.set("height", height)
.set(
"viewBox",
(
shape.shape_bounds.x_min,
shape.shape_bounds.y_min,
width,
height,
),
)
.set("xmlns:xlink", "http://www.w3.org/1999/xlink");
let mut bitmap_defs = HashSet::<CharacterId>::new();
let mut defs = Definitions::new();
let mut num_defs = 0;
let mut svg_paths = vec![];
let (paths, strokes) = swf_shape_to_paths(shape);
for path in paths {
let mut svg_path = SvgPath::new();
svg_path = svg_path.set(
"fill",
match path.fill_style {
FillStyle::Color(Color { r, g, b, a }) => {
format!("rgba({},{},{},{})", r, g, b, f32::from(a) / 255.0)
}
FillStyle::LinearGradient(gradient) => {
let matrix = Matrix::from(gradient.matrix);
let shift = Matrix {
a: 1638.4 / width,
d: 1638.4 / height,
tx: -819.2,
ty: -819.2,
..Default::default()
};
let gradient_matrix = matrix * shift;
let mut svg_gradient = LinearGradient::new()
.set("id", format!("f{}", num_defs))
.set("gradientUnits", "userSpaceOnUse")
.set(
"gradientTransform",
format!(
"matrix({} {} {} {} {} {})",
gradient_matrix.a,
gradient_matrix.b,
gradient_matrix.c,
gradient_matrix.d,
gradient_matrix.tx,
gradient_matrix.ty
),
);
for record in &gradient.records {
let stop = Stop::new()
.set("offset", format!("{}%", f32::from(record.ratio) / 2.55))
.set(
"stop-color",
format!(
"rgba({},{},{},{})",
record.color.r,
record.color.g,
record.color.b,
f32::from(record.color.a) / 255.0
),
);
svg_gradient = svg_gradient.add(stop);
}
defs = defs.add(svg_gradient);
let fill_id = format!("url(#f{})", num_defs);
num_defs += 1;
fill_id
}
FillStyle::RadialGradient(gradient) => {
let matrix = Matrix::from(gradient.matrix);
let shift = Matrix {
a: 1638.4 / width,
d: 1638.4 / height,
tx: -819.2,
ty: -819.2,
..Default::default()
};
let gradient_matrix = matrix * shift;
let mut svg_gradient = RadialGradient::new()
.set("id", format!("f{}", num_defs))
.set("gradientUnits", "userSpaceOnUse")
.set(
"gradientTransform",
format!(
"matrix({} {} {} {} {} {})",
gradient_matrix.a,
gradient_matrix.b,
gradient_matrix.c,
gradient_matrix.d,
gradient_matrix.tx,
gradient_matrix.ty
),
);
for record in &gradient.records {
let stop = Stop::new()
.set("offset", format!("{}%", f32::from(record.ratio) / 2.55))
.set(
"stop-color",
format!(
"rgba({},{},{},{})",
record.color.r, record.color.g, record.color.b, record.color.a
),
);
svg_gradient = svg_gradient.add(stop);
}
defs = defs.add(svg_gradient);
let fill_id = format!("url(#f{})", num_defs);
num_defs += 1;
fill_id
}
FillStyle::FocalGradient {
gradient,
focal_point,
} => {
let matrix = Matrix::from(gradient.matrix);
let shift = Matrix {
a: 1638.4 / width,
d: 1638.4 / height,
tx: -819.2,
ty: -819.2,
..Default::default()
};
let gradient_matrix = matrix * shift;
let mut svg_gradient = RadialGradient::new()
.set("id", format!("f{}", num_defs))
.set("fx", -focal_point)
.set("gradientUnits", "userSpaceOnUse")
.set(
"gradientTransform",
format!(
"matrix({} {} {} {} {} {})",
gradient_matrix.a,
gradient_matrix.b,
gradient_matrix.c,
gradient_matrix.d,
gradient_matrix.tx,
gradient_matrix.ty
),
);
for record in &gradient.records {
let stop = Stop::new()
.set("offset", format!("{}%", f32::from(record.ratio) / 2.55))
.set(
"stop-color",
format!(
"rgba({},{},{},{})",
record.color.r, record.color.g, record.color.b, record.color.a
),
);
svg_gradient = svg_gradient.add(stop);
}
defs = defs.add(svg_gradient);
let fill_id = format!("url(#f{})", num_defs);
num_defs += 1;
fill_id
}
FillStyle::Bitmap { id, matrix, .. } => {
let (bitmap_data, bitmap_width, bitmap_height) =
bitmaps.get(&id).unwrap_or(&("", 0, 0));
if !bitmap_defs.contains(&id) {
let image = Image::new()
.set("width", *bitmap_width)
.set("height", *bitmap_height)
.set("xlink:href", *bitmap_data);
let bitmap_pattern = Pattern::new()
.set("id", format!("b{}", id))
.set("width", *bitmap_width)
.set("height", *bitmap_height)
.set("patternUnits", "userSpaceOnUse")
.add(image);
defs = defs.add(bitmap_pattern);
bitmap_defs.insert(id);
}
log::info!("{:?}", matrix);
let a = Matrix::from(matrix);
// let shift = Matrix {
// a: 1.0 / 20.0,
// a: 1.0 / 20.0,
// d: 1.0 / 20.0,
// tx: 0.0, //-819.2,
// ty: 0.0, //-819.2,
// ..Default::default()
// };
let mut bitmap_matrix = a;
bitmap_matrix.a /= 20.0;
bitmap_matrix.b /= 20.0;
bitmap_matrix.c /= 20.0;
bitmap_matrix.d /= 20.0;
let svg_pattern = Pattern::new()
.set("id", format!("f{}", num_defs))
.set("xlink:href", format!("#b{}", id))
.set(
"patternTransform",
format!(
"matrix({} {} {} {} {} {})",
bitmap_matrix.a,
bitmap_matrix.b,
bitmap_matrix.c,
bitmap_matrix.d,
bitmap_matrix.tx,
bitmap_matrix.ty
),
);
defs = defs.add(svg_pattern);
let fill_id = format!("url(#f{})", num_defs);
num_defs += 1;
fill_id
}
},
);
let mut data = Data::new();
for subpath in &path.subpaths {
//svg_paths.push_str(&format!("M{} {}", subpath.start.0, subpath.start.1));
data = data.move_to(subpath.start);
for edge in &subpath.edges {
match edge {
SubpathEdge::Straight(x, y) => {
data = data.line_to((*x, *y));
}
SubpathEdge::Bezier(cx, cy, ax, ay) => {
data = data.quadratic_curve_to((*cx, *cy, *ax, *ay));
}
}
}
}
svg_path = svg_path.set("d", data);
svg_paths.push(svg_path);
}
for stroke in strokes {
let mut svg_path = SvgPath::new();
let line_style = stroke.line_style.unwrap();
svg_path = svg_path
.set("fill", "none")
.set(
"stroke",
format!(
"rgba({},{},{},{})",
line_style.color.r, line_style.color.g, line_style.color.b, line_style.color.a
),
)
.set("width", f32::from(line_style.width) / 20.0);
let mut data = Data::new();
for subpath in &stroke.subpaths {
data = data.move_to(subpath.start);
for edge in &subpath.edges {
match edge {
SubpathEdge::Straight(x, y) => {
data = data.line_to((*x, *y));
}
SubpathEdge::Bezier(cx, cy, ax, ay) => {
data = data.quadratic_curve_to((*cx, *cy, *ax, *ay));
}
}
}
}
svg_path = svg_path.set("d", data);
svg_paths.push(svg_path);
}
if num_defs > 0 {
document = document.add(defs);
}
for svg_path in svg_paths {
document = document.add(svg_path);
}
document.to_string()
}
struct Stroke {
path: Path,
line_style: LineStyle,
}
// TODO(Herschel): Iterater-ize this.
pub fn swf_shape_to_paths(shape: &Shape) -> (Vec<Path>, Vec<Path>) {
let mut layers = vec![];
let mut paths = HashMap::<u32, Path>::new();
let mut stroke_paths = HashMap::<u32, Path>::new();
let mut x = 0f32;
let mut y = 0f32;
let mut fill_style_0 = 0;
let mut fill_style_1 = 0;
let mut line_style = 0;
let mut fill_styles = &shape.styles.fill_styles;
let mut line_styles = &shape.styles.line_styles;
for record in &shape.shape {
use swf::ShapeRecord::*;
match record {
StyleChange(style_change) => {
if let Some((move_x, move_y)) = style_change.move_to {
x = move_x;
y = move_y;
}
if let Some(i) = style_change.fill_style_0 {
fill_style_0 = i;
}
if let Some(i) = style_change.fill_style_1 {
fill_style_1 = i;
}
if let Some(i) = style_change.line_style {
line_style = i;
}
if let Some(ref new_styles) = style_change.new_styles {
// TODO
layers.push((paths, stroke_paths));
paths = HashMap::new();
stroke_paths = HashMap::new();
fill_styles = &new_styles.fill_styles;
line_styles = &new_styles.line_styles;
}
}
StraightEdge { delta_x, delta_y } => {
if fill_style_0 != 0 {
let path = paths.entry(fill_style_0).or_insert_with(|| {
Path::new(fill_styles[fill_style_0 as usize - 1].clone())
});
path.add_edge((x + delta_x, y + delta_y), SubpathEdge::Straight(x, y));
}
if fill_style_1 != 0 {
let path = paths.entry(fill_style_1).or_insert_with(|| {
Path::new(fill_styles[fill_style_1 as usize - 1].clone())
});
path.add_edge((x, y), SubpathEdge::Straight(x + delta_x, y + delta_y));
}
if line_style != 0 {
let path = stroke_paths.entry(line_style).or_insert_with(|| {
Path::new_stroke(line_styles[line_style as usize - 1].clone())
});
path.add_edge((x, y), SubpathEdge::Straight(x + delta_x, y + delta_y));
}
x += delta_x;
y += delta_y;
}
CurvedEdge {
control_delta_x,
control_delta_y,
anchor_delta_x,
anchor_delta_y,
} => {
if fill_style_0 != 0 {
let path = paths.entry(fill_style_0).or_insert_with(|| {
Path::new(fill_styles[fill_style_0 as usize - 1].clone())
});
path.add_edge(
(
x + control_delta_x + anchor_delta_x,
y + control_delta_y + anchor_delta_y,
),
SubpathEdge::Bezier(x + control_delta_x, y + control_delta_y, x, y),
);
}
if fill_style_1 != 0 {
let path = paths.entry(fill_style_1).or_insert_with(|| {
Path::new(fill_styles[fill_style_1 as usize - 1].clone())
});
path.add_edge(
(x, y),
SubpathEdge::Bezier(
x + control_delta_x,
y + control_delta_y,
x + control_delta_x + anchor_delta_x,
y + control_delta_y + anchor_delta_y,
),
);
}
if line_style != 0 {
let path = stroke_paths.entry(line_style).or_insert_with(|| {
Path::new_stroke(line_styles[line_style as usize - 1].clone())
});
path.add_edge(
(x, y),
SubpathEdge::Bezier(
x + control_delta_x,
y + control_delta_y,
x + control_delta_x + anchor_delta_x,
y + control_delta_y + anchor_delta_y,
),
);
}
x += control_delta_x + anchor_delta_x;
y += control_delta_y + anchor_delta_y;
}
}
}
layers.push((paths, stroke_paths));
let mut out_paths = vec![];
let mut out_strokes = vec![];
for (paths, strokes) in layers {
for (_, path) in paths {
out_paths.push(path);
}
for (_, stroke) in strokes {
out_strokes.push(stroke);
}
}
(out_paths, out_strokes)
}
pub struct Path {
fill_style: FillStyle,
line_style: Option<LineStyle>,
subpaths: Vec<Subpath>,
}
impl Path {
fn new(fill_style: FillStyle) -> Path {
Path {
fill_style,
line_style: None,
subpaths: vec![],
}
}
fn new_stroke(line_style: LineStyle) -> Path {
Path {
fill_style: FillStyle::Color(Color {
r: 0,
g: 0,
b: 0,
a: 0,
}),
line_style: Some(line_style),
subpaths: vec![],
}
}
fn add_edge(&mut self, start: (f32, f32), edge: SubpathEdge) {
let new_subpath = Subpath {
start,
end: match edge {
SubpathEdge::Straight(x, y) => (x, y),
SubpathEdge::Bezier(_cx, _cy, ax, ay) => (ax, ay),
},
edges: {
let mut edges = VecDeque::new();
edges.push_back(edge);
edges
},
};
self.merge_subpath(new_subpath);
}
fn merge_subpath(&mut self, mut subpath: Subpath) {
fn approx_eq(a: (f32, f32), b: (f32, f32)) -> bool {
let dx = a.0 - b.0;
let dy = a.1 - b.1;
const EPSILON: f32 = 0.0001;
dx.abs() < EPSILON && dy.abs() < EPSILON
}
let mut subpath_index = None;
for (i, other) in self.subpaths.iter_mut().enumerate() {
if approx_eq(subpath.end, other.start) {
other.start = subpath.start;
for edge in subpath.edges.iter().rev() {
other.edges.push_front(*edge);
}
subpath_index = Some(i);
break;
}
if approx_eq(other.end, subpath.start) {
other.end = subpath.end;
other.edges.append(&mut subpath.edges);
subpath_index = Some(i);
break;
}
}
if let Some(i) = subpath_index {
let subpath = self.subpaths.swap_remove(i);
self.merge_subpath(subpath);
} else {
self.subpaths.push(subpath);
}
}
}
struct Subpath {
start: (f32, f32),
end: (f32, f32),
edges: VecDeque<SubpathEdge>,
}
#[derive(Copy, Clone)]
enum SubpathEdge {
Straight(f32, f32),
Bezier(f32, f32, f32, f32),
}