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);
|
||||
|
||||
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 {
|
||||
/// Apply a color transformation to this color.
|
||||
fn color_transform(&self, cxform: &ColorTransform) -> Self {
|
||||
|
@ -54,7 +72,7 @@ enum CanvasDrawCommand {
|
|||
Stroke {
|
||||
path: Path2d,
|
||||
line_width: f64,
|
||||
stroke_style: CanvasFillStyle,
|
||||
stroke_style: CanvasStrokeStyle,
|
||||
line_cap: String,
|
||||
line_join: String,
|
||||
miter_limit: f64,
|
||||
|
@ -68,17 +86,41 @@ enum CanvasDrawCommand {
|
|||
},
|
||||
}
|
||||
|
||||
/// Fill style for a canvas path.
|
||||
enum CanvasFillStyle {
|
||||
Color(CanvasColor),
|
||||
Gradient(CanvasGradient),
|
||||
TransformedGradient(TransformedGradient),
|
||||
Pattern(CanvasPattern, bool),
|
||||
Gradient(Gradient),
|
||||
Bitmap(CanvasBitmap),
|
||||
}
|
||||
|
||||
struct TransformedGradient {
|
||||
struct Gradient {
|
||||
gradient: CanvasGradient,
|
||||
gradient_matrix: [f64; 6],
|
||||
inverse_gradient_matrix: DomMatrix,
|
||||
transform: Option<GradientTransform>,
|
||||
}
|
||||
|
||||
/// 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)]
|
||||
|
@ -386,13 +428,26 @@ impl RenderBackend for WebCanvasRenderBackend {
|
|||
MaskState::DrawContent => {
|
||||
let mut line_scale = LineScales::new(&transform.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) {
|
||||
for command in shape.0.iter() {
|
||||
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) => {
|
||||
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.fill_with_path_2d_and_winding(
|
||||
path,
|
||||
|
@ -401,58 +456,48 @@ impl RenderBackend for WebCanvasRenderBackend {
|
|||
}
|
||||
CanvasFillStyle::Gradient(gradient) => {
|
||||
self.set_color_filter(transform);
|
||||
self.context.set_fill_style(gradient);
|
||||
self.context.fill_with_path_2d_and_winding(
|
||||
path,
|
||||
CanvasWindingRule::Evenodd,
|
||||
);
|
||||
self.clear_color_filter();
|
||||
}
|
||||
CanvasFillStyle::TransformedGradient(gradient) => {
|
||||
self.context.set_fill_style(&gradient.gradient);
|
||||
|
||||
if let Some(gradient_transform) = &gradient.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,
|
||||
// then transforming the path itself by the inverse.
|
||||
self.set_color_filter(transform);
|
||||
self.context.set_fill_style(&gradient.gradient);
|
||||
let matrix = &gradient.gradient_matrix;
|
||||
self.context
|
||||
.transform(
|
||||
matrix[0], matrix[1], matrix[2], matrix[3], matrix[4],
|
||||
matrix[5],
|
||||
)
|
||||
.warn_on_error();
|
||||
let matrix = &gradient_transform.matrix;
|
||||
let _ = self.context.transform(
|
||||
matrix[0], matrix[1], matrix[2], matrix[3],
|
||||
matrix[4], matrix[5],
|
||||
);
|
||||
transform_dirty = true;
|
||||
let untransformed_path = Path2d::new().unwrap();
|
||||
untransformed_path.add_path_with_transformation(
|
||||
path,
|
||||
gradient.inverse_gradient_matrix.unchecked_ref(),
|
||||
gradient_transform.inverse_matrix.unchecked_ref(),
|
||||
);
|
||||
self.context.fill_with_path_2d_and_winding(
|
||||
&untransformed_path,
|
||||
CanvasWindingRule::Evenodd,
|
||||
);
|
||||
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();
|
||||
} else {
|
||||
self.context.fill_with_path_2d_and_winding(
|
||||
path,
|
||||
CanvasWindingRule::Evenodd,
|
||||
);
|
||||
}
|
||||
|
||||
self.clear_color_filter();
|
||||
}
|
||||
CanvasFillStyle::Pattern(patt, smoothed) => {
|
||||
CanvasFillStyle::Bitmap(bitmap) => {
|
||||
self.set_color_filter(transform);
|
||||
self.context.set_image_smoothing_enabled(*smoothed);
|
||||
self.context.set_fill_style(patt);
|
||||
self.context.set_image_smoothing_enabled(bitmap.smoothed);
|
||||
self.context.set_fill_style(&bitmap.pattern);
|
||||
self.context.fill_with_path_2d_and_winding(
|
||||
path,
|
||||
CanvasWindingRule::Evenodd,
|
||||
);
|
||||
self.clear_color_filter();
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
CanvasDrawCommand::Stroke {
|
||||
path,
|
||||
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
|
||||
// that the geometry remains untransformed.
|
||||
let _ = self.context.reset_transform();
|
||||
transform_dirty = true;
|
||||
let transformed_path = Path2d::new().unwrap();
|
||||
transformed_path
|
||||
.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);
|
||||
self.context.set_line_width(line_width.into());
|
||||
match stroke_style {
|
||||
CanvasFillStyle::Color(color) => {
|
||||
CanvasStrokeStyle::Color(color) => {
|
||||
let color =
|
||||
color.color_transform(&transform.color_transform);
|
||||
self.context.set_stroke_style(&color.0.into());
|
||||
self.context.stroke_with_path(&transformed_path);
|
||||
}
|
||||
CanvasFillStyle::Gradient(gradient) => {
|
||||
self.set_color_filter(transform);
|
||||
self.context.set_stroke_style(gradient);
|
||||
self.context.stroke_with_path(&transformed_path);
|
||||
self.clear_color_filter();
|
||||
}
|
||||
CanvasFillStyle::TransformedGradient(gradient) => {
|
||||
CanvasStrokeStyle::Gradient(gradient, focal_point) => {
|
||||
// This is the hard case -- the Canvas API provides no good way to transform gradients,
|
||||
// and the inverse-transform trick used above for gradient fills can't be used here
|
||||
// because it will distort the stroke geometry.
|
||||
// Another possibility is to avoid the Path2D API, instead drawing using explicit path
|
||||
// commands (`context.lineTo`), then push the gradient transform, and finally stroke
|
||||
// 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.context.set_stroke_style(&gradient.gradient);
|
||||
self.context.stroke_with_path(&transformed_path);
|
||||
self.clear_color_filter();
|
||||
}
|
||||
CanvasFillStyle::Pattern(patt, smoothed) => {
|
||||
self.context.set_image_smoothing_enabled(*smoothed);
|
||||
self.context.set_stroke_style(patt);
|
||||
}
|
||||
CanvasStrokeStyle::Bitmap(bitmap) => {
|
||||
// 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.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);
|
||||
|
||||
for path in &shape.paths {
|
||||
let (style, commands, is_fill, is_closed) = match &path {
|
||||
match path {
|
||||
DrawPath::Fill {
|
||||
style, commands, ..
|
||||
} => (*style, commands, true, false),
|
||||
DrawPath::Stroke {
|
||||
style,
|
||||
commands,
|
||||
is_closed,
|
||||
} => (style.fill_style(), commands, false, *is_closed),
|
||||
};
|
||||
commands, style, ..
|
||||
} => {
|
||||
let canvas_path = Path2d::new().unwrap();
|
||||
canvas_path.add_path_with_transformation(
|
||||
&draw_commands_to_path2d(commands, false),
|
||||
bounds_viewbox_matrix.unchecked_ref(),
|
||||
);
|
||||
|
||||
let fill_style = match style {
|
||||
FillStyle::Color(Color { r, g, b, a }) => CanvasFillStyle::Color(CanvasColor(
|
||||
format!("rgba({},{},{},{})", r, g, b, f32::from(*a) / 255.0),
|
||||
*r,
|
||||
*g,
|
||||
*b,
|
||||
*a,
|
||||
)),
|
||||
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::Color(color) => CanvasFillStyle::Color(color.into()),
|
||||
FillStyle::LinearGradient(gradient) => CanvasFillStyle::Gradient(
|
||||
create_linear_gradient(context, gradient, true).unwrap(),
|
||||
),
|
||||
FillStyle::RadialGradient(gradient) => CanvasFillStyle::Gradient(
|
||||
create_radial_gradient(context, gradient, 0.0, true).unwrap(),
|
||||
),
|
||||
FillStyle::FocalGradient {
|
||||
gradient,
|
||||
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 {
|
||||
id,
|
||||
matrix,
|
||||
is_smoothed,
|
||||
is_repeating,
|
||||
} => {
|
||||
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"
|
||||
let bitmap = if let Ok(bitmap) = create_bitmap_pattern(
|
||||
*id,
|
||||
*matrix,
|
||||
*is_smoothed,
|
||||
*is_repeating,
|
||||
bitmap_source,
|
||||
bitmaps,
|
||||
context,
|
||||
) {
|
||||
bitmap
|
||||
} 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;
|
||||
};
|
||||
|
||||
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))
|
||||
}
|
||||
CanvasFillStyle::Bitmap(bitmap)
|
||||
}
|
||||
};
|
||||
|
||||
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 {
|
||||
path: canvas_path,
|
||||
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() {
|
||||
LineCapStyle::Round => "round",
|
||||
LineCapStyle::Square => "square",
|
||||
|
@ -792,7 +877,7 @@ fn swf_shape_to_canvas_commands(
|
|||
canvas_data.0.push(CanvasDrawCommand::Stroke {
|
||||
path: canvas_path,
|
||||
line_width: style.width().to_pixels(),
|
||||
stroke_style: fill_style,
|
||||
stroke_style,
|
||||
line_cap: line_cap.to_string(),
|
||||
line_join: line_join.to_string(),
|
||||
miter_limit: miter_limit as f64 / 20.0,
|
||||
|
@ -813,14 +898,14 @@ fn create_linear_gradient(
|
|||
context: &CanvasRenderingContext2d,
|
||||
gradient: &swf::Gradient,
|
||||
is_fill: bool,
|
||||
) -> Result<CanvasFillStyle, JsError> {
|
||||
) -> Result<Gradient, JsError> {
|
||||
// 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).
|
||||
let transformed = if is_fill {
|
||||
let dot = gradient.matrix.a * gradient.matrix.c + gradient.matrix.b * gradient.matrix.d;
|
||||
dot.to_f32().abs() > GRADIENT_TRANSFORM_THRESHOLD
|
||||
} else {
|
||||
// TODO: Gradient transforms don't work correctly with strokes.
|
||||
// Complex gradient transforms can't apply to strokes; fall back to simple transforms.
|
||||
false
|
||||
};
|
||||
let create_fn = |matrix: swf::Matrix, gradient_scale: f64| {
|
||||
|
@ -844,7 +929,7 @@ fn create_radial_gradient(
|
|||
gradient: &swf::Gradient,
|
||||
focal_point: f64,
|
||||
is_fill: bool,
|
||||
) -> Result<CanvasFillStyle, JsError> {
|
||||
) -> Result<Gradient, JsError> {
|
||||
// Canvas radial gradients can not be elliptical or skewed, so transform if there
|
||||
// is a non-uniform scale or skew.
|
||||
// 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.b + gradient.matrix.c).to_f32().abs() > GRADIENT_TRANSFORM_THRESHOLD
|
||||
} else {
|
||||
// TODO: Gradient transforms don't work correctly with strokes.
|
||||
// Complex gradient transforms can't apply to strokes; fall back to simple transforms.
|
||||
false
|
||||
};
|
||||
let create_fn = |matrix: swf::Matrix, gradient_scale: f64| {
|
||||
|
@ -893,7 +978,7 @@ fn swf_to_canvas_gradient(
|
|||
swf_gradient: &swf::Gradient,
|
||||
transformed: bool,
|
||||
mut create_gradient_fn: impl FnMut(swf::Matrix, f64) -> Result<CanvasGradient, JsError>,
|
||||
) -> Result<CanvasFillStyle, JsError> {
|
||||
) -> Result<Gradient, JsError> {
|
||||
let matrix = if transformed {
|
||||
// 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.
|
||||
|
@ -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,
|
||||
// and then transform the path itself by the inverse.
|
||||
let matrix = swf_gradient.matrix.to_dom_matrix();
|
||||
let inverse_gradient_matrix = matrix.inverse();
|
||||
Ok(CanvasFillStyle::TransformedGradient(TransformedGradient {
|
||||
gradient: canvas_gradient,
|
||||
gradient_matrix: [
|
||||
let inverse_matrix = matrix.inverse();
|
||||
GradientTransform {
|
||||
matrix: [
|
||||
matrix.a(),
|
||||
matrix.b(),
|
||||
matrix.c(),
|
||||
|
@ -998,10 +1084,51 @@ fn swf_to_canvas_gradient(
|
|||
matrix.e(),
|
||||
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 {
|
||||
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