canvas: Fix transforming of gradient/bitmap strokes
This commit is contained in:
parent
ef838a3536
commit
6f142c21fa
|
@ -35,6 +35,24 @@ struct ShapeData(Vec<CanvasDrawCommand>);
|
||||||
|
|
||||||
struct CanvasColor(String, u8, u8, u8, u8);
|
struct CanvasColor(String, u8, u8, u8, u8);
|
||||||
|
|
||||||
|
impl From<&Color> for CanvasColor {
|
||||||
|
fn from(color: &Color) -> CanvasColor {
|
||||||
|
CanvasColor(
|
||||||
|
format!(
|
||||||
|
"rgba({},{},{},{})",
|
||||||
|
color.r,
|
||||||
|
color.g,
|
||||||
|
color.b,
|
||||||
|
f32::from(color.a) / 255.0
|
||||||
|
),
|
||||||
|
color.r,
|
||||||
|
color.g,
|
||||||
|
color.b,
|
||||||
|
color.a,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl CanvasColor {
|
impl CanvasColor {
|
||||||
/// Apply a color transformation to this color.
|
/// Apply a color transformation to this color.
|
||||||
fn color_transform(&self, cxform: &ColorTransform) -> Self {
|
fn color_transform(&self, cxform: &ColorTransform) -> Self {
|
||||||
|
@ -54,7 +72,7 @@ enum CanvasDrawCommand {
|
||||||
Stroke {
|
Stroke {
|
||||||
path: Path2d,
|
path: Path2d,
|
||||||
line_width: f64,
|
line_width: f64,
|
||||||
stroke_style: CanvasFillStyle,
|
stroke_style: CanvasStrokeStyle,
|
||||||
line_cap: String,
|
line_cap: String,
|
||||||
line_join: String,
|
line_join: String,
|
||||||
miter_limit: f64,
|
miter_limit: f64,
|
||||||
|
@ -68,17 +86,41 @@ enum CanvasDrawCommand {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fill style for a canvas path.
|
||||||
enum CanvasFillStyle {
|
enum CanvasFillStyle {
|
||||||
Color(CanvasColor),
|
Color(CanvasColor),
|
||||||
Gradient(CanvasGradient),
|
Gradient(Gradient),
|
||||||
TransformedGradient(TransformedGradient),
|
Bitmap(CanvasBitmap),
|
||||||
Pattern(CanvasPattern, bool),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TransformedGradient {
|
struct Gradient {
|
||||||
gradient: CanvasGradient,
|
gradient: CanvasGradient,
|
||||||
gradient_matrix: [f64; 6],
|
transform: Option<GradientTransform>,
|
||||||
inverse_gradient_matrix: DomMatrix,
|
}
|
||||||
|
|
||||||
|
/// A "complex" gradient transform, such as elliptical or skewed gradients.
|
||||||
|
///
|
||||||
|
/// Canvas does not provide an API for arbitrary gradient transforms, so we cheat by applying the
|
||||||
|
/// inverse of the gradient transform to the path, and then drawing the path with the gradient transform.
|
||||||
|
/// This results in an non-transformed shape with a transformed gradient.
|
||||||
|
struct GradientTransform {
|
||||||
|
matrix: [f64; 6],
|
||||||
|
inverse_matrix: DomMatrix,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stroke style for a canvas path.
|
||||||
|
///
|
||||||
|
/// Gradients are handled differently for strokes vs. fills.
|
||||||
|
enum CanvasStrokeStyle {
|
||||||
|
Color(CanvasColor),
|
||||||
|
Gradient(swf::Gradient, Option<f64>),
|
||||||
|
Bitmap(CanvasBitmap),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CanvasBitmap {
|
||||||
|
pattern: CanvasPattern,
|
||||||
|
matrix: Matrix,
|
||||||
|
smoothed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
@ -386,13 +428,26 @@ impl RenderBackend for WebCanvasRenderBackend {
|
||||||
MaskState::DrawContent => {
|
MaskState::DrawContent => {
|
||||||
let mut line_scale = LineScales::new(&transform.matrix);
|
let mut line_scale = LineScales::new(&transform.matrix);
|
||||||
let dom_matrix = transform.matrix.to_dom_matrix();
|
let dom_matrix = transform.matrix.to_dom_matrix();
|
||||||
self.set_transform(&transform.matrix);
|
let mut transform_dirty = true;
|
||||||
if let Some(shape) = self.shapes.get(shape.0) {
|
if let Some(shape) = self.shapes.get(shape.0) {
|
||||||
for command in shape.0.iter() {
|
for command in shape.0.iter() {
|
||||||
match command {
|
match command {
|
||||||
CanvasDrawCommand::Fill { path, fill_style } => match fill_style {
|
CanvasDrawCommand::Fill { path, fill_style } => {
|
||||||
|
if transform_dirty {
|
||||||
|
let _ = self.context.set_transform(
|
||||||
|
transform.matrix.a.into(),
|
||||||
|
transform.matrix.b.into(),
|
||||||
|
transform.matrix.c.into(),
|
||||||
|
transform.matrix.d.into(),
|
||||||
|
transform.matrix.tx.to_pixels(),
|
||||||
|
transform.matrix.ty.to_pixels(),
|
||||||
|
);
|
||||||
|
transform_dirty = false;
|
||||||
|
}
|
||||||
|
match fill_style {
|
||||||
CanvasFillStyle::Color(color) => {
|
CanvasFillStyle::Color(color) => {
|
||||||
let color = color.color_transform(&transform.color_transform);
|
let color =
|
||||||
|
color.color_transform(&transform.color_transform);
|
||||||
self.context.set_fill_style(&color.0.into());
|
self.context.set_fill_style(&color.0.into());
|
||||||
self.context.fill_with_path_2d_and_winding(
|
self.context.fill_with_path_2d_and_winding(
|
||||||
path,
|
path,
|
||||||
|
@ -401,58 +456,48 @@ impl RenderBackend for WebCanvasRenderBackend {
|
||||||
}
|
}
|
||||||
CanvasFillStyle::Gradient(gradient) => {
|
CanvasFillStyle::Gradient(gradient) => {
|
||||||
self.set_color_filter(transform);
|
self.set_color_filter(transform);
|
||||||
self.context.set_fill_style(gradient);
|
self.context.set_fill_style(&gradient.gradient);
|
||||||
self.context.fill_with_path_2d_and_winding(
|
|
||||||
path,
|
if let Some(gradient_transform) = &gradient.transform {
|
||||||
CanvasWindingRule::Evenodd,
|
|
||||||
);
|
|
||||||
self.clear_color_filter();
|
|
||||||
}
|
|
||||||
CanvasFillStyle::TransformedGradient(gradient) => {
|
|
||||||
// Canvas has no easy way to draw gradients with an arbitrary transform,
|
// Canvas has no easy way to draw gradients with an arbitrary transform,
|
||||||
// but we can fake it by pushing the gradient's transform to the canvas,
|
// but we can fake it by pushing the gradient's transform to the canvas,
|
||||||
// then transforming the path itself by the inverse.
|
// then transforming the path itself by the inverse.
|
||||||
self.set_color_filter(transform);
|
let matrix = &gradient_transform.matrix;
|
||||||
self.context.set_fill_style(&gradient.gradient);
|
let _ = self.context.transform(
|
||||||
let matrix = &gradient.gradient_matrix;
|
matrix[0], matrix[1], matrix[2], matrix[3],
|
||||||
self.context
|
matrix[4], matrix[5],
|
||||||
.transform(
|
);
|
||||||
matrix[0], matrix[1], matrix[2], matrix[3], matrix[4],
|
transform_dirty = true;
|
||||||
matrix[5],
|
|
||||||
)
|
|
||||||
.warn_on_error();
|
|
||||||
let untransformed_path = Path2d::new().unwrap();
|
let untransformed_path = Path2d::new().unwrap();
|
||||||
untransformed_path.add_path_with_transformation(
|
untransformed_path.add_path_with_transformation(
|
||||||
path,
|
path,
|
||||||
gradient.inverse_gradient_matrix.unchecked_ref(),
|
gradient_transform.inverse_matrix.unchecked_ref(),
|
||||||
);
|
);
|
||||||
self.context.fill_with_path_2d_and_winding(
|
self.context.fill_with_path_2d_and_winding(
|
||||||
&untransformed_path,
|
&untransformed_path,
|
||||||
CanvasWindingRule::Evenodd,
|
CanvasWindingRule::Evenodd,
|
||||||
);
|
);
|
||||||
self.context
|
} else {
|
||||||
.set_transform(
|
self.context.fill_with_path_2d_and_winding(
|
||||||
transform.matrix.a.into(),
|
path,
|
||||||
transform.matrix.b.into(),
|
CanvasWindingRule::Evenodd,
|
||||||
transform.matrix.c.into(),
|
);
|
||||||
transform.matrix.d.into(),
|
}
|
||||||
transform.matrix.tx.to_pixels(),
|
|
||||||
transform.matrix.ty.to_pixels(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
self.clear_color_filter();
|
self.clear_color_filter();
|
||||||
}
|
}
|
||||||
CanvasFillStyle::Pattern(patt, smoothed) => {
|
CanvasFillStyle::Bitmap(bitmap) => {
|
||||||
self.set_color_filter(transform);
|
self.set_color_filter(transform);
|
||||||
self.context.set_image_smoothing_enabled(*smoothed);
|
self.context.set_image_smoothing_enabled(bitmap.smoothed);
|
||||||
self.context.set_fill_style(patt);
|
self.context.set_fill_style(&bitmap.pattern);
|
||||||
self.context.fill_with_path_2d_and_winding(
|
self.context.fill_with_path_2d_and_winding(
|
||||||
path,
|
path,
|
||||||
CanvasWindingRule::Evenodd,
|
CanvasWindingRule::Evenodd,
|
||||||
);
|
);
|
||||||
self.clear_color_filter();
|
self.clear_color_filter();
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
}
|
||||||
CanvasDrawCommand::Stroke {
|
CanvasDrawCommand::Stroke {
|
||||||
path,
|
path,
|
||||||
line_width,
|
line_width,
|
||||||
|
@ -466,6 +511,7 @@ impl RenderBackend for WebCanvasRenderBackend {
|
||||||
// Instead, reset the canvas transform, and apply the transform to the stroke path directly so
|
// Instead, reset the canvas transform, and apply the transform to the stroke path directly so
|
||||||
// that the geometry remains untransformed.
|
// that the geometry remains untransformed.
|
||||||
let _ = self.context.reset_transform();
|
let _ = self.context.reset_transform();
|
||||||
|
transform_dirty = true;
|
||||||
let transformed_path = Path2d::new().unwrap();
|
let transformed_path = Path2d::new().unwrap();
|
||||||
transformed_path
|
transformed_path
|
||||||
.add_path_with_transformation(path, dom_matrix.unchecked_ref());
|
.add_path_with_transformation(path, dom_matrix.unchecked_ref());
|
||||||
|
@ -478,41 +524,60 @@ impl RenderBackend for WebCanvasRenderBackend {
|
||||||
line_scale.transform_width(*line_width as f32, *scale_mode);
|
line_scale.transform_width(*line_width as f32, *scale_mode);
|
||||||
self.context.set_line_width(line_width.into());
|
self.context.set_line_width(line_width.into());
|
||||||
match stroke_style {
|
match stroke_style {
|
||||||
CanvasFillStyle::Color(color) => {
|
CanvasStrokeStyle::Color(color) => {
|
||||||
let color =
|
let color =
|
||||||
color.color_transform(&transform.color_transform);
|
color.color_transform(&transform.color_transform);
|
||||||
self.context.set_stroke_style(&color.0.into());
|
self.context.set_stroke_style(&color.0.into());
|
||||||
self.context.stroke_with_path(&transformed_path);
|
self.context.stroke_with_path(&transformed_path);
|
||||||
}
|
}
|
||||||
CanvasFillStyle::Gradient(gradient) => {
|
CanvasStrokeStyle::Gradient(gradient, focal_point) => {
|
||||||
self.set_color_filter(transform);
|
// This is the hard case -- the Canvas API provides no good way to transform gradients,
|
||||||
self.context.set_stroke_style(gradient);
|
// and the inverse-transform trick used above for gradient fills can't be used here
|
||||||
self.context.stroke_with_path(&transformed_path);
|
// because it will distort the stroke geometry.
|
||||||
self.clear_color_filter();
|
// Another possibility is to avoid the Path2D API, instead drawing using explicit path
|
||||||
}
|
// commands (`context.lineTo`), then push the gradient transform, and finally stroke
|
||||||
CanvasFillStyle::TransformedGradient(gradient) => {
|
// the path using `stroke()`. But this will be tons of JS calls if there are many strokes.
|
||||||
|
// So let's settle for allocating a new canvas gradient that is a best-effort match of the
|
||||||
|
// the desired transform. This will not match Flash exactly, but should be relatively rare.
|
||||||
|
let mut gradient = gradient.clone();
|
||||||
|
gradient.matrix = (transform.matrix
|
||||||
|
* Matrix::from(gradient.matrix))
|
||||||
|
.into();
|
||||||
|
let gradient = match focal_point {
|
||||||
|
Some(focal_point) => create_radial_gradient(
|
||||||
|
&self.context,
|
||||||
|
&gradient,
|
||||||
|
*focal_point,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
None => create_linear_gradient(
|
||||||
|
&self.context,
|
||||||
|
&gradient,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
if let Ok(gradient) = gradient {
|
||||||
self.set_color_filter(transform);
|
self.set_color_filter(transform);
|
||||||
self.context.set_stroke_style(&gradient.gradient);
|
self.context.set_stroke_style(&gradient.gradient);
|
||||||
self.context.stroke_with_path(&transformed_path);
|
self.context.stroke_with_path(&transformed_path);
|
||||||
self.clear_color_filter();
|
self.clear_color_filter();
|
||||||
}
|
}
|
||||||
CanvasFillStyle::Pattern(patt, smoothed) => {
|
}
|
||||||
self.context.set_image_smoothing_enabled(*smoothed);
|
CanvasStrokeStyle::Bitmap(bitmap) => {
|
||||||
self.context.set_stroke_style(patt);
|
// Set the CanvasPattern's matrix to the concatenated transform.
|
||||||
|
let bitmap_matrix = transform.matrix
|
||||||
|
* bitmap.matrix
|
||||||
|
* Matrix::scale(0.05, 0.05);
|
||||||
|
bitmap.pattern.set_transform(
|
||||||
|
bitmap_matrix.to_dom_matrix().unchecked_ref(),
|
||||||
|
);
|
||||||
|
self.set_color_filter(transform);
|
||||||
|
self.context.set_image_smoothing_enabled(bitmap.smoothed);
|
||||||
|
self.context.set_stroke_style(&bitmap.pattern);
|
||||||
self.context.stroke_with_path(&transformed_path);
|
self.context.stroke_with_path(&transformed_path);
|
||||||
self.clear_color_filter();
|
self.clear_color_filter();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
self.context
|
|
||||||
.set_transform(
|
|
||||||
transform.matrix.a.into(),
|
|
||||||
transform.matrix.b.into(),
|
|
||||||
transform.matrix.c.into(),
|
|
||||||
transform.matrix.d.into(),
|
|
||||||
transform.matrix.tx.to_pixels(),
|
|
||||||
transform.matrix.ty.to_pixels(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -700,85 +765,105 @@ fn swf_shape_to_canvas_commands(
|
||||||
bounds_viewbox_matrix.set_d(1.0 / 20.0);
|
bounds_viewbox_matrix.set_d(1.0 / 20.0);
|
||||||
|
|
||||||
for path in &shape.paths {
|
for path in &shape.paths {
|
||||||
let (style, commands, is_fill, is_closed) = match &path {
|
match path {
|
||||||
DrawPath::Fill {
|
DrawPath::Fill {
|
||||||
style, commands, ..
|
commands, style, ..
|
||||||
} => (*style, commands, true, false),
|
} => {
|
||||||
DrawPath::Stroke {
|
let canvas_path = Path2d::new().unwrap();
|
||||||
style,
|
canvas_path.add_path_with_transformation(
|
||||||
commands,
|
&draw_commands_to_path2d(commands, false),
|
||||||
is_closed,
|
bounds_viewbox_matrix.unchecked_ref(),
|
||||||
} => (style.fill_style(), commands, false, *is_closed),
|
);
|
||||||
};
|
|
||||||
let fill_style = match style {
|
let fill_style = match style {
|
||||||
FillStyle::Color(Color { r, g, b, a }) => CanvasFillStyle::Color(CanvasColor(
|
FillStyle::Color(color) => CanvasFillStyle::Color(color.into()),
|
||||||
format!("rgba({},{},{},{})", r, g, b, f32::from(*a) / 255.0),
|
FillStyle::LinearGradient(gradient) => CanvasFillStyle::Gradient(
|
||||||
*r,
|
create_linear_gradient(context, gradient, true).unwrap(),
|
||||||
*g,
|
),
|
||||||
*b,
|
FillStyle::RadialGradient(gradient) => CanvasFillStyle::Gradient(
|
||||||
*a,
|
create_radial_gradient(context, gradient, 0.0, true).unwrap(),
|
||||||
)),
|
),
|
||||||
FillStyle::LinearGradient(gradient) => {
|
|
||||||
create_linear_gradient(context, gradient, is_fill).unwrap()
|
|
||||||
}
|
|
||||||
FillStyle::RadialGradient(gradient) => {
|
|
||||||
create_radial_gradient(context, gradient, 0.0, is_fill).unwrap()
|
|
||||||
}
|
|
||||||
FillStyle::FocalGradient {
|
FillStyle::FocalGradient {
|
||||||
gradient,
|
gradient,
|
||||||
focal_point,
|
focal_point,
|
||||||
} => create_radial_gradient(context, gradient, focal_point.to_f64(), is_fill).unwrap(),
|
} => CanvasFillStyle::Gradient(
|
||||||
|
create_radial_gradient(context, gradient, focal_point.to_f64(), true)
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
FillStyle::Bitmap {
|
FillStyle::Bitmap {
|
||||||
id,
|
id,
|
||||||
matrix,
|
matrix,
|
||||||
is_smoothed,
|
is_smoothed,
|
||||||
is_repeating,
|
is_repeating,
|
||||||
} => {
|
} => {
|
||||||
if let Some(bitmap) = bitmap_source
|
let bitmap = if let Ok(bitmap) = create_bitmap_pattern(
|
||||||
.bitmap(*id)
|
*id,
|
||||||
.and_then(|bitmap| bitmaps.get(&bitmap.handle))
|
*matrix,
|
||||||
{
|
*is_smoothed,
|
||||||
let repeat = if !*is_repeating {
|
*is_repeating,
|
||||||
// NOTE: The WebGL backend does clamping in this case, just like
|
bitmap_source,
|
||||||
// Flash Player, but CanvasPattern has no such option...
|
bitmaps,
|
||||||
"no-repeat"
|
context,
|
||||||
|
) {
|
||||||
|
bitmap
|
||||||
} else {
|
} else {
|
||||||
"repeat"
|
|
||||||
};
|
|
||||||
|
|
||||||
let bitmap_pattern = if let Ok(Some(bitmap_pattern)) =
|
|
||||||
context.create_pattern_with_html_canvas_element(&bitmap.canvas, repeat)
|
|
||||||
{
|
|
||||||
bitmap_pattern
|
|
||||||
} else {
|
|
||||||
log::warn!("Unable to create bitmap pattern for bitmap ID {}", id);
|
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
CanvasFillStyle::Bitmap(bitmap)
|
||||||
bitmap_pattern.set_transform(matrix.to_dom_matrix().unchecked_ref());
|
|
||||||
|
|
||||||
CanvasFillStyle::Pattern(bitmap_pattern, *is_smoothed)
|
|
||||||
} else {
|
|
||||||
log::error!("Couldn't fill shape with unknown bitmap {}", id);
|
|
||||||
CanvasFillStyle::Color(CanvasColor("rgba(0,0,0,0)".to_string(), 0, 0, 0, 0))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let canvas_path = Path2d::new().unwrap();
|
|
||||||
canvas_path.add_path_with_transformation(
|
|
||||||
&draw_commands_to_path2d(commands, is_closed),
|
|
||||||
bounds_viewbox_matrix.unchecked_ref(),
|
|
||||||
);
|
|
||||||
|
|
||||||
match path {
|
|
||||||
DrawPath::Fill { .. } => {
|
|
||||||
canvas_data.0.push(CanvasDrawCommand::Fill {
|
canvas_data.0.push(CanvasDrawCommand::Fill {
|
||||||
path: canvas_path,
|
path: canvas_path,
|
||||||
fill_style,
|
fill_style,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
DrawPath::Stroke { style, .. } => {
|
DrawPath::Stroke {
|
||||||
|
commands,
|
||||||
|
style,
|
||||||
|
is_closed,
|
||||||
|
} => {
|
||||||
|
let canvas_path = Path2d::new().unwrap();
|
||||||
|
canvas_path.add_path_with_transformation(
|
||||||
|
&draw_commands_to_path2d(commands, *is_closed),
|
||||||
|
bounds_viewbox_matrix.unchecked_ref(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let stroke_style = match style.fill_style() {
|
||||||
|
FillStyle::Color(color) => CanvasStrokeStyle::Color(color.into()),
|
||||||
|
FillStyle::LinearGradient(gradient) => {
|
||||||
|
CanvasStrokeStyle::Gradient(gradient.clone(), None)
|
||||||
|
}
|
||||||
|
FillStyle::RadialGradient(gradient) => {
|
||||||
|
CanvasStrokeStyle::Gradient(gradient.clone(), Some(0.0))
|
||||||
|
}
|
||||||
|
FillStyle::FocalGradient {
|
||||||
|
gradient,
|
||||||
|
focal_point,
|
||||||
|
} => CanvasStrokeStyle::Gradient(gradient.clone(), Some(focal_point.to_f64())),
|
||||||
|
FillStyle::Bitmap {
|
||||||
|
id,
|
||||||
|
matrix,
|
||||||
|
is_smoothed,
|
||||||
|
is_repeating,
|
||||||
|
} => {
|
||||||
|
let bitmap = if let Ok(bitmap) = create_bitmap_pattern(
|
||||||
|
*id,
|
||||||
|
*matrix,
|
||||||
|
*is_smoothed,
|
||||||
|
*is_repeating,
|
||||||
|
bitmap_source,
|
||||||
|
bitmaps,
|
||||||
|
context,
|
||||||
|
) {
|
||||||
|
bitmap
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
CanvasStrokeStyle::Bitmap(bitmap)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let line_cap = match style.start_cap() {
|
let line_cap = match style.start_cap() {
|
||||||
LineCapStyle::Round => "round",
|
LineCapStyle::Round => "round",
|
||||||
LineCapStyle::Square => "square",
|
LineCapStyle::Square => "square",
|
||||||
|
@ -792,7 +877,7 @@ fn swf_shape_to_canvas_commands(
|
||||||
canvas_data.0.push(CanvasDrawCommand::Stroke {
|
canvas_data.0.push(CanvasDrawCommand::Stroke {
|
||||||
path: canvas_path,
|
path: canvas_path,
|
||||||
line_width: style.width().to_pixels(),
|
line_width: style.width().to_pixels(),
|
||||||
stroke_style: fill_style,
|
stroke_style,
|
||||||
line_cap: line_cap.to_string(),
|
line_cap: line_cap.to_string(),
|
||||||
line_join: line_join.to_string(),
|
line_join: line_join.to_string(),
|
||||||
miter_limit: miter_limit as f64 / 20.0,
|
miter_limit: miter_limit as f64 / 20.0,
|
||||||
|
@ -813,14 +898,14 @@ fn create_linear_gradient(
|
||||||
context: &CanvasRenderingContext2d,
|
context: &CanvasRenderingContext2d,
|
||||||
gradient: &swf::Gradient,
|
gradient: &swf::Gradient,
|
||||||
is_fill: bool,
|
is_fill: bool,
|
||||||
) -> Result<CanvasFillStyle, JsError> {
|
) -> Result<Gradient, JsError> {
|
||||||
// Canvas linear gradients are configured via the line endpoints, so we only need
|
// Canvas linear gradients are configured via the line endpoints, so we only need
|
||||||
// to transform it if the basis is not orthogonal (skew in the transform).
|
// to transform it if the basis is not orthogonal (skew in the transform).
|
||||||
let transformed = if is_fill {
|
let transformed = if is_fill {
|
||||||
let dot = gradient.matrix.a * gradient.matrix.c + gradient.matrix.b * gradient.matrix.d;
|
let dot = gradient.matrix.a * gradient.matrix.c + gradient.matrix.b * gradient.matrix.d;
|
||||||
dot.to_f32().abs() > GRADIENT_TRANSFORM_THRESHOLD
|
dot.to_f32().abs() > GRADIENT_TRANSFORM_THRESHOLD
|
||||||
} else {
|
} else {
|
||||||
// TODO: Gradient transforms don't work correctly with strokes.
|
// Complex gradient transforms can't apply to strokes; fall back to simple transforms.
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
let create_fn = |matrix: swf::Matrix, gradient_scale: f64| {
|
let create_fn = |matrix: swf::Matrix, gradient_scale: f64| {
|
||||||
|
@ -844,7 +929,7 @@ fn create_radial_gradient(
|
||||||
gradient: &swf::Gradient,
|
gradient: &swf::Gradient,
|
||||||
focal_point: f64,
|
focal_point: f64,
|
||||||
is_fill: bool,
|
is_fill: bool,
|
||||||
) -> Result<CanvasFillStyle, JsError> {
|
) -> Result<Gradient, JsError> {
|
||||||
// Canvas radial gradients can not be elliptical or skewed, so transform if there
|
// Canvas radial gradients can not be elliptical or skewed, so transform if there
|
||||||
// is a non-uniform scale or skew.
|
// is a non-uniform scale or skew.
|
||||||
// A scale rotation matrix is always of the form:
|
// A scale rotation matrix is always of the form:
|
||||||
|
@ -854,7 +939,7 @@ fn create_radial_gradient(
|
||||||
(gradient.matrix.a - gradient.matrix.d).to_f32().abs() > GRADIENT_TRANSFORM_THRESHOLD
|
(gradient.matrix.a - gradient.matrix.d).to_f32().abs() > GRADIENT_TRANSFORM_THRESHOLD
|
||||||
|| (gradient.matrix.b + gradient.matrix.c).to_f32().abs() > GRADIENT_TRANSFORM_THRESHOLD
|
|| (gradient.matrix.b + gradient.matrix.c).to_f32().abs() > GRADIENT_TRANSFORM_THRESHOLD
|
||||||
} else {
|
} else {
|
||||||
// TODO: Gradient transforms don't work correctly with strokes.
|
// Complex gradient transforms can't apply to strokes; fall back to simple transforms.
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
let create_fn = |matrix: swf::Matrix, gradient_scale: f64| {
|
let create_fn = |matrix: swf::Matrix, gradient_scale: f64| {
|
||||||
|
@ -893,7 +978,7 @@ fn swf_to_canvas_gradient(
|
||||||
swf_gradient: &swf::Gradient,
|
swf_gradient: &swf::Gradient,
|
||||||
transformed: bool,
|
transformed: bool,
|
||||||
mut create_gradient_fn: impl FnMut(swf::Matrix, f64) -> Result<CanvasGradient, JsError>,
|
mut create_gradient_fn: impl FnMut(swf::Matrix, f64) -> Result<CanvasGradient, JsError>,
|
||||||
) -> Result<CanvasFillStyle, JsError> {
|
) -> Result<Gradient, JsError> {
|
||||||
let matrix = if transformed {
|
let matrix = if transformed {
|
||||||
// When we are rendering a complex gradient, the gradient transform is handled later by
|
// When we are rendering a complex gradient, the gradient transform is handled later by
|
||||||
// transforming the path before rendering; so use the indentity matrix here.
|
// transforming the path before rendering; so use the indentity matrix here.
|
||||||
|
@ -983,14 +1068,15 @@ fn swf_to_canvas_gradient(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if transformed {
|
Ok(Gradient {
|
||||||
|
gradient: canvas_gradient,
|
||||||
|
transform: transformed.then(|| {
|
||||||
// When we render this gradient, we will push the gradient's transform to the canvas,
|
// When we render this gradient, we will push the gradient's transform to the canvas,
|
||||||
// and then transform the path itself by the inverse.
|
// and then transform the path itself by the inverse.
|
||||||
let matrix = swf_gradient.matrix.to_dom_matrix();
|
let matrix = swf_gradient.matrix.to_dom_matrix();
|
||||||
let inverse_gradient_matrix = matrix.inverse();
|
let inverse_matrix = matrix.inverse();
|
||||||
Ok(CanvasFillStyle::TransformedGradient(TransformedGradient {
|
GradientTransform {
|
||||||
gradient: canvas_gradient,
|
matrix: [
|
||||||
gradient_matrix: [
|
|
||||||
matrix.a(),
|
matrix.a(),
|
||||||
matrix.b(),
|
matrix.b(),
|
||||||
matrix.c(),
|
matrix.c(),
|
||||||
|
@ -998,10 +1084,51 @@ fn swf_to_canvas_gradient(
|
||||||
matrix.e(),
|
matrix.e(),
|
||||||
matrix.f(),
|
matrix.f(),
|
||||||
],
|
],
|
||||||
inverse_gradient_matrix,
|
inverse_matrix,
|
||||||
}))
|
}
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts an SWF bitmap fill to a canvas pattern.
|
||||||
|
fn create_bitmap_pattern(
|
||||||
|
id: swf::CharacterId,
|
||||||
|
matrix: swf::Matrix,
|
||||||
|
is_smoothed: bool,
|
||||||
|
is_repeating: bool,
|
||||||
|
bitmap_source: &dyn BitmapSource,
|
||||||
|
bitmaps: &FnvHashMap<BitmapHandle, BitmapData>,
|
||||||
|
context: &CanvasRenderingContext2d,
|
||||||
|
) -> Result<CanvasBitmap, Error> {
|
||||||
|
if let Some(bitmap) = bitmap_source
|
||||||
|
.bitmap(id)
|
||||||
|
.and_then(|bitmap| bitmaps.get(&bitmap.handle))
|
||||||
|
{
|
||||||
|
let repeat = if !is_repeating {
|
||||||
|
// NOTE: The WebGL backend does clamping in this case, just like
|
||||||
|
// Flash Player, but CanvasPattern has no such option...
|
||||||
|
"no-repeat"
|
||||||
} else {
|
} else {
|
||||||
Ok(CanvasFillStyle::Gradient(canvas_gradient))
|
"repeat"
|
||||||
|
};
|
||||||
|
|
||||||
|
let pattern = match context.create_pattern_with_html_canvas_element(&bitmap.canvas, repeat)
|
||||||
|
{
|
||||||
|
Ok(Some(pattern)) => pattern,
|
||||||
|
_ => {
|
||||||
|
log::warn!("Unable to create bitmap pattern for bitmap ID {}", id);
|
||||||
|
return Err("Unable to create bitmap pattern".into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
pattern.set_transform(matrix.to_dom_matrix().unchecked_ref());
|
||||||
|
Ok(CanvasBitmap {
|
||||||
|
pattern,
|
||||||
|
matrix: matrix.into(),
|
||||||
|
smoothed: is_smoothed,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
log::warn!("Couldn't fill shape with unknown bitmap {}", id);
|
||||||
|
Err("Unable to create bitmap pattern".into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue