canvas: Remove SVG rendering path
Remove `CanvasDrawCommand::DrawImage` and all associated machinery that generates an SVG.
This commit is contained in:
parent
ce044409e4
commit
d0aa7b4df2
|
@ -3090,7 +3090,6 @@ dependencies = [
|
||||||
"png",
|
"png",
|
||||||
"ruffle_core",
|
"ruffle_core",
|
||||||
"ruffle_web_common",
|
"ruffle_web_common",
|
||||||
"svg",
|
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
@ -3471,12 +3470,6 @@ version = "0.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "svg"
|
|
||||||
version = "0.10.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e72d8b19ab05827afefcca66bf47040c1e66a0901eb814784c77d4ec118bd309"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "swf"
|
name = "swf"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
|
|
|
@ -14,7 +14,6 @@ fnv = "1.0.7"
|
||||||
js-sys = "0.3.57"
|
js-sys = "0.3.57"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
ruffle_web_common = { path = "../../web/common" }
|
ruffle_web_common = { path = "../../web/common" }
|
||||||
svg = "0.10.0"
|
|
||||||
percent-encoding = "2.1.0"
|
percent-encoding = "2.1.0"
|
||||||
png = "0.17.5"
|
png = "0.17.5"
|
||||||
wasm-bindgen = "=0.2.80"
|
wasm-bindgen = "=0.2.80"
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
use ruffle_core::backend::render::{
|
use ruffle_core::backend::render::{
|
||||||
swf::{self, CharacterId, GradientInterpolation, GradientSpread},
|
swf, Bitmap, BitmapFormat, BitmapHandle, BitmapInfo, BitmapSource, Color, JpegTagFormat,
|
||||||
Bitmap, BitmapFormat, BitmapHandle, BitmapInfo, BitmapSource, Color, JpegTagFormat,
|
|
||||||
NullBitmapSource, RenderBackend, ShapeHandle, Transform,
|
NullBitmapSource, RenderBackend, ShapeHandle, Transform,
|
||||||
};
|
};
|
||||||
use ruffle_core::color_transform::ColorTransform;
|
use ruffle_core::color_transform::ColorTransform;
|
||||||
use ruffle_core::matrix::Matrix;
|
use ruffle_core::matrix::Matrix;
|
||||||
use ruffle_core::shape_utils::{DistilledShape, DrawCommand};
|
use ruffle_core::shape_utils::{DistilledShape, DrawCommand};
|
||||||
use ruffle_web_common::{JsError, JsResult};
|
use ruffle_web_common::{JsError, JsResult};
|
||||||
use std::cell::{Ref, RefCell};
|
use std::cell::RefCell;
|
||||||
use wasm_bindgen::{Clamped, JsCast};
|
use wasm_bindgen::{Clamped, JsCast};
|
||||||
use web_sys::{
|
use web_sys::{
|
||||||
CanvasGradient, CanvasPattern, CanvasRenderingContext2d, CanvasWindingRule, DomMatrix, Element,
|
CanvasGradient, CanvasPattern, CanvasRenderingContext2d, CanvasWindingRule, DomMatrix, Element,
|
||||||
|
@ -69,13 +68,6 @@ enum CanvasDrawCommand {
|
||||||
path: Path2d,
|
path: Path2d,
|
||||||
fill_style: CanvasFillStyle,
|
fill_style: CanvasFillStyle,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// A command to draw a particular image (such as an SVG)
|
|
||||||
DrawImage {
|
|
||||||
image: HtmlImageElement,
|
|
||||||
x_min: f64,
|
|
||||||
y_min: f64,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum CanvasFillStyle {
|
enum CanvasFillStyle {
|
||||||
|
@ -186,45 +178,6 @@ impl BitmapData {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts an RGBA image into a PNG encoded as a data URI referencing a Blob.
|
|
||||||
fn bitmap_to_png_data_uri(bitmap: Bitmap) -> Result<String, Box<dyn std::error::Error>> {
|
|
||||||
use png::Encoder;
|
|
||||||
let mut png_data: Vec<u8> = vec![];
|
|
||||||
{
|
|
||||||
let mut encoder = Encoder::new(&mut png_data, bitmap.width, bitmap.height);
|
|
||||||
encoder.set_depth(png::BitDepth::Eight);
|
|
||||||
let data = match bitmap.data {
|
|
||||||
BitmapFormat::Rgba(mut data) => {
|
|
||||||
ruffle_core::backend::render::unmultiply_alpha_rgba(&mut data[..]);
|
|
||||||
encoder.set_color(png::ColorType::Rgba);
|
|
||||||
data
|
|
||||||
}
|
|
||||||
BitmapFormat::Rgb(data) => {
|
|
||||||
encoder.set_color(png::ColorType::Rgb);
|
|
||||||
data
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let mut writer = encoder.write_header()?;
|
|
||||||
writer.write_image_data(&data)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(format!(
|
|
||||||
"data:image/png;base64,{}",
|
|
||||||
&base64::encode(&png_data[..])
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_or_compute_data_uri(&self) -> Ref<Option<String>> {
|
|
||||||
{
|
|
||||||
let mut uri = self.data_uri.borrow_mut();
|
|
||||||
|
|
||||||
if uri.is_none() {
|
|
||||||
*uri = Some(Self::bitmap_to_png_data_uri(self.get_pixels().unwrap()).unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.data_uri.borrow()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WebCanvasRenderBackend {
|
impl WebCanvasRenderBackend {
|
||||||
|
@ -533,25 +486,14 @@ impl RenderBackend for WebCanvasRenderBackend {
|
||||||
bitmap_source: &dyn BitmapSource,
|
bitmap_source: &dyn BitmapSource,
|
||||||
) -> ShapeHandle {
|
) -> ShapeHandle {
|
||||||
let handle = ShapeHandle(self.shapes.len());
|
let handle = ShapeHandle(self.shapes.len());
|
||||||
|
|
||||||
let data = swf_shape_to_canvas_commands(
|
let data = swf_shape_to_canvas_commands(
|
||||||
&shape,
|
&shape,
|
||||||
bitmap_source,
|
bitmap_source,
|
||||||
&self.bitmaps,
|
&self.bitmaps,
|
||||||
self.pixelated_property_value,
|
self.pixelated_property_value,
|
||||||
&self.context,
|
&self.context,
|
||||||
)
|
);
|
||||||
.unwrap_or_else(|| {
|
|
||||||
swf_shape_to_svg(
|
|
||||||
shape,
|
|
||||||
bitmap_source,
|
|
||||||
&self.bitmaps,
|
|
||||||
self.pixelated_property_value,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
self.shapes.push(data);
|
self.shapes.push(data);
|
||||||
|
|
||||||
handle
|
handle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -567,15 +509,7 @@ impl RenderBackend for WebCanvasRenderBackend {
|
||||||
&self.bitmaps,
|
&self.bitmaps,
|
||||||
self.pixelated_property_value,
|
self.pixelated_property_value,
|
||||||
&self.context,
|
&self.context,
|
||||||
)
|
);
|
||||||
.unwrap_or_else(|| {
|
|
||||||
swf_shape_to_svg(
|
|
||||||
shape,
|
|
||||||
bitmap_source,
|
|
||||||
&self.bitmaps,
|
|
||||||
self.pixelated_property_value,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
self.shapes[handle.0] = data;
|
self.shapes[handle.0] = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -800,17 +734,6 @@ impl RenderBackend for WebCanvasRenderBackend {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
CanvasDrawCommand::DrawImage {
|
|
||||||
image,
|
|
||||||
x_min,
|
|
||||||
y_min,
|
|
||||||
} => {
|
|
||||||
self.set_color_filter(transform);
|
|
||||||
let _ = self
|
|
||||||
.context
|
|
||||||
.draw_image_with_html_image_element(image, *x_min, *y_min);
|
|
||||||
self.clear_color_filter();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -936,445 +859,6 @@ impl RenderBackend for WebCanvasRenderBackend {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::cognitive_complexity)]
|
|
||||||
fn swf_shape_to_svg(
|
|
||||||
shape: DistilledShape,
|
|
||||||
bitmap_source: &dyn BitmapSource,
|
|
||||||
bitmaps: &[BitmapData],
|
|
||||||
pixelated_property_value: &str,
|
|
||||||
) -> ShapeData {
|
|
||||||
use fnv::FnvHashSet;
|
|
||||||
use ruffle_core::shape_utils::DrawPath;
|
|
||||||
use svg::node::element::{
|
|
||||||
path::Data, Definitions, Filter, Image, LinearGradient, Path as SvgPath, Pattern,
|
|
||||||
RadialGradient, Stop,
|
|
||||||
};
|
|
||||||
use svg::Document;
|
|
||||||
use swf::{FillStyle, LineCapStyle, LineJoinStyle};
|
|
||||||
|
|
||||||
// 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).to_pixels() as f32,
|
|
||||||
1.0,
|
|
||||||
),
|
|
||||||
f32::max(
|
|
||||||
(shape.shape_bounds.y_max - shape.shape_bounds.y_min).to_pixels() as f32,
|
|
||||||
1.0,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
let mut document = Document::new()
|
|
||||||
.set("width", width)
|
|
||||||
.set("height", height)
|
|
||||||
.set(
|
|
||||||
"viewBox",
|
|
||||||
(
|
|
||||||
shape.shape_bounds.x_min.get(),
|
|
||||||
shape.shape_bounds.y_min.get(),
|
|
||||||
(shape.shape_bounds.x_max - shape.shape_bounds.x_min).get(),
|
|
||||||
(shape.shape_bounds.y_max - shape.shape_bounds.y_min).get(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
// preserveAspectRatio must be off or Firefox will fudge with the dimensions when we draw an image onto canvas.
|
|
||||||
.set("preserveAspectRatio", "none")
|
|
||||||
.set("xmlns:xlink", "http://www.w3.org/1999/xlink");
|
|
||||||
|
|
||||||
let width = (shape.shape_bounds.x_max - shape.shape_bounds.x_min).get() as f32;
|
|
||||||
let height = (shape.shape_bounds.y_max - shape.shape_bounds.y_min).get() as f32;
|
|
||||||
|
|
||||||
let mut bitmap_defs: FnvHashSet<CharacterId> = FnvHashSet::default();
|
|
||||||
|
|
||||||
let mut defs = Definitions::new();
|
|
||||||
let mut num_defs = 0;
|
|
||||||
let mut has_linear_rgb_gradient = false;
|
|
||||||
|
|
||||||
let mut svg_paths = Vec::with_capacity(shape.paths.len());
|
|
||||||
for path in shape.paths {
|
|
||||||
let mut svg_path = SvgPath::new();
|
|
||||||
let (style, commands) = match &path {
|
|
||||||
DrawPath::Fill { style, commands } => (*style, commands),
|
|
||||||
DrawPath::Stroke {
|
|
||||||
style, commands, ..
|
|
||||||
} => (style.fill_style(), commands),
|
|
||||||
};
|
|
||||||
let fill = match style {
|
|
||||||
FillStyle::Color(Color { r, g, b, a }) => {
|
|
||||||
format!("rgba({},{},{},{})", r, g, b, f32::from(*a) / 255.0)
|
|
||||||
}
|
|
||||||
FillStyle::LinearGradient(gradient) => {
|
|
||||||
let shift = Matrix {
|
|
||||||
a: 32768.0 / width,
|
|
||||||
d: 32768.0 / height,
|
|
||||||
tx: swf::Twips::new(-16384),
|
|
||||||
ty: swf::Twips::new(-16384),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let gradient_matrix = Matrix::from(gradient.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.get(),
|
|
||||||
gradient_matrix.ty.get()
|
|
||||||
),
|
|
||||||
);
|
|
||||||
svg_gradient = match gradient.spread {
|
|
||||||
GradientSpread::Pad => svg_gradient, // default
|
|
||||||
GradientSpread::Reflect => svg_gradient.set("spreadMethod", "reflect"),
|
|
||||||
GradientSpread::Repeat => svg_gradient.set("spreadMethod", "repeat"),
|
|
||||||
};
|
|
||||||
if gradient.interpolation == GradientInterpolation::LinearRgb {
|
|
||||||
has_linear_rgb_gradient = true;
|
|
||||||
svg_path = svg_path.set("filter", "url('#_linearrgb')");
|
|
||||||
}
|
|
||||||
for record in &gradient.records {
|
|
||||||
let color = if gradient.interpolation == GradientInterpolation::LinearRgb {
|
|
||||||
srgb_to_linear(record.color.clone())
|
|
||||||
} else {
|
|
||||||
record.color.clone()
|
|
||||||
};
|
|
||||||
let stop = Stop::new()
|
|
||||||
.set("offset", format!("{}%", f32::from(record.ratio) / 2.55))
|
|
||||||
.set(
|
|
||||||
"stop-color",
|
|
||||||
format!(
|
|
||||||
"rgba({},{},{},{})",
|
|
||||||
color.r,
|
|
||||||
color.g,
|
|
||||||
color.b,
|
|
||||||
f32::from(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 shift = Matrix {
|
|
||||||
a: 32768.0,
|
|
||||||
d: 32768.0,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let gradient_matrix = Matrix::from(gradient.matrix) * shift;
|
|
||||||
|
|
||||||
let mut svg_gradient = RadialGradient::new()
|
|
||||||
.set("id", format!("f{}", num_defs))
|
|
||||||
.set("gradientUnits", "userSpaceOnUse")
|
|
||||||
.set("cx", "0")
|
|
||||||
.set("cy", "0")
|
|
||||||
.set("r", "0.5")
|
|
||||||
.set(
|
|
||||||
"gradientTransform",
|
|
||||||
format!(
|
|
||||||
"matrix({} {} {} {} {} {})",
|
|
||||||
gradient_matrix.a,
|
|
||||||
gradient_matrix.b,
|
|
||||||
gradient_matrix.c,
|
|
||||||
gradient_matrix.d,
|
|
||||||
gradient_matrix.tx.get(),
|
|
||||||
gradient_matrix.ty.get()
|
|
||||||
),
|
|
||||||
);
|
|
||||||
svg_gradient = match gradient.spread {
|
|
||||||
GradientSpread::Pad => svg_gradient, // default
|
|
||||||
GradientSpread::Reflect => svg_gradient.set("spreadMethod", "reflect"),
|
|
||||||
GradientSpread::Repeat => svg_gradient.set("spreadMethod", "repeat"),
|
|
||||||
};
|
|
||||||
if gradient.interpolation == GradientInterpolation::LinearRgb {
|
|
||||||
has_linear_rgb_gradient = true;
|
|
||||||
svg_path = svg_path.set("filter", "url('#_linearrgb')");
|
|
||||||
}
|
|
||||||
for record in &gradient.records {
|
|
||||||
let color = if gradient.interpolation == GradientInterpolation::LinearRgb {
|
|
||||||
srgb_to_linear(record.color.clone())
|
|
||||||
} else {
|
|
||||||
record.color.clone()
|
|
||||||
};
|
|
||||||
let stop = Stop::new()
|
|
||||||
.set("offset", format!("{}%", f32::from(record.ratio) / 2.55))
|
|
||||||
.set(
|
|
||||||
"stop-color",
|
|
||||||
format!(
|
|
||||||
"rgba({},{},{},{})",
|
|
||||||
color.r,
|
|
||||||
color.g,
|
|
||||||
color.b,
|
|
||||||
f32::from(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::FocalGradient {
|
|
||||||
gradient,
|
|
||||||
focal_point,
|
|
||||||
} => {
|
|
||||||
let shift = Matrix {
|
|
||||||
a: 32768.0,
|
|
||||||
d: 32768.0,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let gradient_matrix = Matrix::from(gradient.matrix) * shift;
|
|
||||||
|
|
||||||
let mut svg_gradient = RadialGradient::new()
|
|
||||||
.set("id", format!("f{}", num_defs))
|
|
||||||
.set("fx", focal_point.to_f32() / 2.0)
|
|
||||||
.set("gradientUnits", "userSpaceOnUse")
|
|
||||||
.set("cx", "0")
|
|
||||||
.set("cy", "0")
|
|
||||||
.set("r", "0.5")
|
|
||||||
.set(
|
|
||||||
"gradientTransform",
|
|
||||||
format!(
|
|
||||||
"matrix({} {} {} {} {} {})",
|
|
||||||
gradient_matrix.a,
|
|
||||||
gradient_matrix.b,
|
|
||||||
gradient_matrix.c,
|
|
||||||
gradient_matrix.d,
|
|
||||||
gradient_matrix.tx.get(),
|
|
||||||
gradient_matrix.ty.get()
|
|
||||||
),
|
|
||||||
);
|
|
||||||
svg_gradient = match gradient.spread {
|
|
||||||
GradientSpread::Pad => svg_gradient, // default
|
|
||||||
GradientSpread::Reflect => svg_gradient.set("spreadMethod", "reflect"),
|
|
||||||
GradientSpread::Repeat => svg_gradient.set("spreadMethod", "repeat"),
|
|
||||||
};
|
|
||||||
if gradient.interpolation == GradientInterpolation::LinearRgb {
|
|
||||||
has_linear_rgb_gradient = true;
|
|
||||||
svg_path = svg_path.set("filter", "url('#_linearrgb')");
|
|
||||||
}
|
|
||||||
for record in &gradient.records {
|
|
||||||
let color = if gradient.interpolation == GradientInterpolation::LinearRgb {
|
|
||||||
srgb_to_linear(record.color.clone())
|
|
||||||
} else {
|
|
||||||
record.color.clone()
|
|
||||||
};
|
|
||||||
let stop = Stop::new()
|
|
||||||
.set("offset", format!("{}%", f32::from(record.ratio) / 2.55))
|
|
||||||
.set(
|
|
||||||
"stop-color",
|
|
||||||
format!(
|
|
||||||
"rgba({},{},{},{})",
|
|
||||||
color.r,
|
|
||||||
color.g,
|
|
||||||
color.b,
|
|
||||||
f32::from(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::Bitmap {
|
|
||||||
id,
|
|
||||||
matrix,
|
|
||||||
is_smoothed,
|
|
||||||
is_repeating,
|
|
||||||
} => {
|
|
||||||
if let Some(bitmap) = bitmap_source
|
|
||||||
.bitmap(*id)
|
|
||||||
.and_then(|bitmap| bitmaps.get(bitmap.handle.0))
|
|
||||||
{
|
|
||||||
if !bitmap_defs.contains(id) {
|
|
||||||
let mut image = Image::new()
|
|
||||||
.set("width", bitmap.width)
|
|
||||||
.set("height", bitmap.height)
|
|
||||||
.set(
|
|
||||||
"xlink:href",
|
|
||||||
bitmap.get_or_compute_data_uri().as_ref().unwrap().clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if !*is_smoothed {
|
|
||||||
image = image.set("image-rendering", pixelated_property_value);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut bitmap_pattern = Pattern::new()
|
|
||||||
.set("id", format!("b{}", id))
|
|
||||||
.set("patternUnits", "userSpaceOnUse");
|
|
||||||
|
|
||||||
if !*is_repeating {
|
|
||||||
bitmap_pattern = bitmap_pattern
|
|
||||||
.set("width", bitmap.width)
|
|
||||||
.set("height", bitmap.height);
|
|
||||||
} else {
|
|
||||||
bitmap_pattern = bitmap_pattern
|
|
||||||
.set("width", bitmap.width)
|
|
||||||
.set("height", bitmap.height)
|
|
||||||
.set("viewBox", format!("0 0 {} {}", bitmap.width, bitmap.height));
|
|
||||||
}
|
|
||||||
|
|
||||||
bitmap_pattern = bitmap_pattern.add(image);
|
|
||||||
|
|
||||||
defs = defs.add(bitmap_pattern);
|
|
||||||
bitmap_defs.insert(*id);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log::error!("Couldn't fill shape with unknown bitmap {}", id);
|
|
||||||
}
|
|
||||||
|
|
||||||
let svg_pattern = Pattern::new()
|
|
||||||
.set("id", format!("f{}", num_defs))
|
|
||||||
.set("xlink:href", format!("#b{}", id))
|
|
||||||
.set(
|
|
||||||
"patternTransform",
|
|
||||||
format!(
|
|
||||||
"matrix({} {} {} {} {} {})",
|
|
||||||
matrix.a,
|
|
||||||
matrix.b,
|
|
||||||
matrix.c,
|
|
||||||
matrix.d,
|
|
||||||
matrix.tx.get(),
|
|
||||||
matrix.ty.get()
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
defs = defs.add(svg_pattern);
|
|
||||||
|
|
||||||
let fill_id = format!("url(#f{})", num_defs);
|
|
||||||
num_defs += 1;
|
|
||||||
fill_id
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut data = Data::new();
|
|
||||||
for command in commands {
|
|
||||||
data = match command {
|
|
||||||
DrawCommand::MoveTo { x, y } => data.move_to((x.get(), y.get())),
|
|
||||||
DrawCommand::LineTo { x, y } => data.line_to((x.get(), y.get())),
|
|
||||||
DrawCommand::CurveTo { x1, y1, x2, y2 } => {
|
|
||||||
data.quadratic_curve_to((x1.get(), y1.get(), x2.get(), y2.get()))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
match path {
|
|
||||||
DrawPath::Fill { .. } => {
|
|
||||||
svg_path = svg_path
|
|
||||||
.set("fill", fill)
|
|
||||||
.set("fill-rule", "evenodd")
|
|
||||||
.set("d", data);
|
|
||||||
svg_paths.push(svg_path);
|
|
||||||
}
|
|
||||||
DrawPath::Stroke {
|
|
||||||
style, is_closed, ..
|
|
||||||
} => {
|
|
||||||
// Flash always renders strokes with a minimum width of 1 pixel (20 twips).
|
|
||||||
// Additionally, many SWFs use the "hairline" stroke setting, which sets the stroke's width
|
|
||||||
// to 1 twip. Because of the minimum, this will effectively make the stroke nearly-always render
|
|
||||||
// as 1 pixel wide.
|
|
||||||
// SVG doesn't have a minimum and can render strokes at fractional widths, so these hairline
|
|
||||||
// strokes end up rendering very faintly if we use the actual width of 1 twip.
|
|
||||||
// Therefore, we clamp the stroke width to 1 pixel (20 twips). This won't be 100% accurate
|
|
||||||
// if the shape is scaled, but it looks much closer to the Flash Player.
|
|
||||||
let stroke_width = std::cmp::max(style.width().get(), 20);
|
|
||||||
svg_path = svg_path
|
|
||||||
.set("fill", "none")
|
|
||||||
.set("stroke", fill)
|
|
||||||
.set("stroke-width", stroke_width)
|
|
||||||
.set(
|
|
||||||
"stroke-linecap",
|
|
||||||
match style.start_cap() {
|
|
||||||
LineCapStyle::Round => "round",
|
|
||||||
LineCapStyle::Square => "square",
|
|
||||||
LineCapStyle::None => "butt",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.set(
|
|
||||||
"stroke-linejoin",
|
|
||||||
match style.join_style() {
|
|
||||||
LineJoinStyle::Round => "round",
|
|
||||||
LineJoinStyle::Bevel => "bevel",
|
|
||||||
LineJoinStyle::Miter(_) => "miter",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if let LineJoinStyle::Miter(miter_limit) = style.join_style() {
|
|
||||||
svg_path = svg_path.set("stroke-miterlimit", miter_limit.to_f32());
|
|
||||||
}
|
|
||||||
|
|
||||||
if is_closed {
|
|
||||||
data = data.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
svg_path = svg_path.set("d", data);
|
|
||||||
svg_paths.push(svg_path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this shape contains a gradient in linear RGB space, add a filter to do the color space adjustment.
|
|
||||||
// We have to use a filter because browser don't seem to implement the `color-interpolation` SVG property.
|
|
||||||
if has_linear_rgb_gradient {
|
|
||||||
// Add a filter to convert from linear space to sRGB space.
|
|
||||||
let mut filter = Filter::new()
|
|
||||||
.set("id", "_linearrgb")
|
|
||||||
.set("color-interpolation-filters", "sRGB");
|
|
||||||
let text = svg::node::Text::new(
|
|
||||||
r#"
|
|
||||||
<feComponentTransfer>
|
|
||||||
<feFuncR type="gamma" exponent="0.4545454545"></feFuncR>
|
|
||||||
<feFuncG type="gamma" exponent="0.4545454545"></feFuncG>
|
|
||||||
<feFuncB type="gamma" exponent="0.4545454545"></feFuncB>
|
|
||||||
</feComponentTransfer>
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
filter = filter.add(text);
|
|
||||||
defs = defs.add(filter);
|
|
||||||
num_defs += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if num_defs > 0 {
|
|
||||||
document = document.add(defs);
|
|
||||||
}
|
|
||||||
|
|
||||||
for svg_path in svg_paths {
|
|
||||||
document = document.add(svg_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
|
|
||||||
let svg = document.to_string();
|
|
||||||
let svg_encoded = format!(
|
|
||||||
"data:image/svg+xml,{}",
|
|
||||||
utf8_percent_encode(&svg, NON_ALPHANUMERIC)
|
|
||||||
);
|
|
||||||
|
|
||||||
let image = HtmlImageElement::new().unwrap();
|
|
||||||
image.set_src(&svg_encoded);
|
|
||||||
|
|
||||||
let mut data = ShapeData(vec![]);
|
|
||||||
data.0.push(CanvasDrawCommand::DrawImage {
|
|
||||||
image,
|
|
||||||
x_min: shape.shape_bounds.x_min.to_pixels(),
|
|
||||||
y_min: shape.shape_bounds.y_min.to_pixels(),
|
|
||||||
});
|
|
||||||
|
|
||||||
data
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert a series of `DrawCommands` to a `Path2d` shape.
|
/// Convert a series of `DrawCommands` to a `Path2d` shape.
|
||||||
///
|
///
|
||||||
/// The path can be optionally closed by setting `is_closed` to `true`.
|
/// The path can be optionally closed by setting `is_closed` to `true`.
|
||||||
|
@ -1409,7 +893,7 @@ fn swf_shape_to_canvas_commands(
|
||||||
bitmaps: &[BitmapData],
|
bitmaps: &[BitmapData],
|
||||||
_pixelated_property_value: &str,
|
_pixelated_property_value: &str,
|
||||||
context: &CanvasRenderingContext2d,
|
context: &CanvasRenderingContext2d,
|
||||||
) -> Option<ShapeData> {
|
) -> ShapeData {
|
||||||
use ruffle_core::shape_utils::DrawPath;
|
use ruffle_core::shape_utils::DrawPath;
|
||||||
use swf::{FillStyle, LineCapStyle, LineJoinStyle};
|
use swf::{FillStyle, LineCapStyle, LineJoinStyle};
|
||||||
|
|
||||||
|
@ -1490,12 +974,18 @@ fn swf_shape_to_canvas_commands(
|
||||||
};
|
};
|
||||||
|
|
||||||
let bitmap_pattern = match &bitmap.image {
|
let bitmap_pattern = match &bitmap.image {
|
||||||
BitmapDataStorage::ImageElement(elem) => context
|
BitmapDataStorage::ImageElement(elem) => {
|
||||||
.create_pattern_with_html_image_element(elem, repeat)
|
context.create_pattern_with_html_image_element(elem, repeat)
|
||||||
.expect("pattern creation success")?,
|
}
|
||||||
BitmapDataStorage::CanvasElement(canvas, _context) => context
|
BitmapDataStorage::CanvasElement(canvas, _context) => {
|
||||||
.create_pattern_with_html_canvas_element(canvas, repeat)
|
context.create_pattern_with_html_canvas_element(canvas, repeat)
|
||||||
.expect("pattern creation success")?,
|
}
|
||||||
|
};
|
||||||
|
let bitmap_pattern = if let Ok(Some(bitmap_pattern)) = bitmap_pattern {
|
||||||
|
bitmap_pattern
|
||||||
|
} else {
|
||||||
|
log::warn!("Unable to create bitmap pattern for bitmap ID {}", id);
|
||||||
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let a = *matrix;
|
let a = *matrix;
|
||||||
|
@ -1565,8 +1055,7 @@ fn swf_shape_to_canvas_commands(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
canvas_data
|
||||||
Some(canvas_data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts an SWF color from sRGB space to linear color space.
|
/// Converts an SWF color from sRGB space to linear color space.
|
||||||
|
|
Loading…
Reference in New Issue