diff --git a/components/gfx/filters.rs b/components/gfx/filters.rs index 948f2cdfc3d7..cfabcf20324d 100644 --- a/components/gfx/filters.rs +++ b/components/gfx/filters.rs @@ -8,15 +8,18 @@ use azure::AzFloat; use azure::azure_hl::{ColorMatrixAttribute, ColorMatrixInput, CompositeInput, DrawTarget}; use azure::azure_hl::{FilterNode, FilterType, LinearTransferAttribute, LinearTransferInput}; use azure::azure_hl::{Matrix5x4, TableTransferAttribute, TableTransferInput}; +use azure::azure_hl::{GaussianBlurAttribute, GaussianBlurInput}; use std::num::Float; use style::computed_values::filter; +use util::geometry::Au; /// Creates a filter pipeline from a set of CSS filters. Returns the destination end of the filter /// pipeline and the opacity. pub fn create_filters(draw_target: &DrawTarget, temporary_draw_target: &DrawTarget, - style_filters: &filter::T) + style_filters: &filter::T, + accumulated_blur_radius: &mut Au) -> (FilterNode, AzFloat) { let mut opacity = 1.0; let mut filter = draw_target.create_filter(FilterType::Composite); @@ -91,6 +94,14 @@ pub fn create_filters(draw_target: &DrawTarget, contrast.set_input(LinearTransferInput, &filter); filter = contrast } + filter::Filter::Blur(amount) => { + *accumulated_blur_radius = accumulated_blur_radius.clone() + amount; + let amount = amount.to_frac32_px(); + let blur = draw_target.create_filter(FilterType::GaussianBlur); + blur.set_attribute(GaussianBlurAttribute::StdDeviation(amount)); + blur.set_input(GaussianBlurInput, &filter); + filter = blur + } } } (filter, opacity) @@ -107,6 +118,23 @@ pub fn temporary_draw_target_needed_for_style_filters(filters: &filter::T) -> bo false } +// If there is one or more blur filters, we need to know the blur ammount +// to expand the draw target size. +pub fn calculate_accumulated_blur(style_filters: &filter::T) -> Au { + let mut accum_blur = Au::new(0); + for style_filter in style_filters.filters.iter() { + match *style_filter { + filter::Filter::Blur(amount) => { + accum_blur = accum_blur.clone() + amount; + } + _ => continue, + } + } + + accum_blur +} + + /// Creates a grayscale 5x4 color matrix per CSS-FILTERS ยง 12.1.1. fn grayscale(amount: AzFloat) -> Matrix5x4 { Matrix5x4 { diff --git a/components/gfx/paint_context.rs b/components/gfx/paint_context.rs index 0cbd5a739b63..4f2d6c6153bf 100644 --- a/components/gfx/paint_context.rs +++ b/components/gfx/paint_context.rs @@ -909,12 +909,30 @@ impl<'a> PaintContext<'a> { } // FIXME(pcwalton): This surface might be bigger than necessary and waste memory. - let size = self.draw_target.get_size(); - let size = Size2D(size.width, size.height); + let size = self.draw_target.get_size(); //Az size. + let mut size = Size2D(size.width, size.height); //Geom::Size. + + // Pre-calculate if there is a blur expansion need. + let accum_blur = filters::calculate_accumulated_blur(filters); + let mut matrix = self.draw_target.get_transform(); + if accum_blur > Au(0) { + // Set the correct size. + let side_inflation = accum_blur * BLUR_INFLATION_FACTOR; + size = Size2D(size.width + (side_inflation.to_nearest_px() * 2) as i32, size.height + (side_inflation.to_nearest_px() * 2) as i32); + + // Calculate the transform matrix. + let old_transform = self.draw_target.get_transform(); + let inflated_size = Rect(Point2D(0.0, 0.0), Size2D(size.width as AzFloat, + size.height as AzFloat)); + let temporary_draw_target_bounds = old_transform.transform_rect(&inflated_size); + matrix = Matrix2D::identity().translate(-temporary_draw_target_bounds.origin.x as AzFloat, + -temporary_draw_target_bounds.origin.y as AzFloat).mul(&old_transform); + } let temporary_draw_target = self.draw_target.create_similar_draw_target(&size, self.draw_target.get_format()); - temporary_draw_target.set_transform(&self.draw_target.get_transform()); + + temporary_draw_target.set_transform(&matrix); temporary_draw_target } @@ -932,18 +950,36 @@ impl<'a> PaintContext<'a> { // Set up transforms. let old_transform = self.draw_target.get_transform(); self.draw_target.set_transform(&Matrix2D::identity()); - temporary_draw_target.set_transform(&Matrix2D::identity()); + let rect = Rect(Point2D(0.0, 0.0), self.draw_target.get_size().to_azure_size()); + + let rect_temporary = Rect(Point2D(0.0, 0.0), temporary_draw_target.get_size().to_azure_size()); // Create the Azure filter pipeline. + let mut accum_blur = Au(0); let (filter_node, opacity) = filters::create_filters(&self.draw_target, temporary_draw_target, - filters); + filters, + &mut accum_blur); // Perform the blit operation. - let rect = Rect(Point2D(0.0, 0.0), self.draw_target.get_size().to_azure_size()); let mut draw_options = DrawOptions::new(opacity, 0); draw_options.set_composition_op(blend_mode.to_azure_composition_op()); - self.draw_target.draw_filter(&filter_node, &rect, &rect.origin, draw_options); + + // If there is a blur expansion, shift the transform and update the size. + if accum_blur > Au(0) { + // Remove both the transient clip and the stacking context clip, because we may need to + // draw outside the stacking context's clip. + self.remove_transient_clip_if_applicable(); + self.pop_clip_if_applicable(); + + debug!("######### use expanded Rect."); + self.draw_target.draw_filter(&filter_node, &rect_temporary, &rect_temporary.origin, draw_options); + self.push_clip_if_applicable(); + } else { + debug!("######### use regular Rect."); + self.draw_target.draw_filter(&filter_node, &rect, &rect.origin, draw_options); + } + self.draw_target.set_transform(&old_transform); } diff --git a/components/style/properties.mako.rs b/components/style/properties.mako.rs index 26e0bdb0f449..084b9b31b235 100644 --- a/components/style/properties.mako.rs +++ b/components/style/properties.mako.rs @@ -2695,22 +2695,38 @@ pub mod longhands { <%self:longhand name="filter"> - use values::specified::Angle; - pub use self::computed_value::T as SpecifiedValue; - pub use self::computed_value::Filter; - use values::computed::ComputedValueAsSpecified; + //pub use self::computed_value::T as SpecifiedValue; + use values::computed::{Context, ToComputedValue}; + use values::specified::{Angle, Length}; + use values::CSSFloat; + use cssparser::{self, ToCss}; + use text_writer::{self, TextWriter}; - impl ComputedValueAsSpecified for SpecifiedValue {} + #[derive(Clone, PartialEq)] + pub struct SpecifiedValue(Vec); + + // TODO(pcwalton): `drop-shadow` + #[derive(Clone, PartialEq, Debug)] + pub enum SpecifiedFilter { + Blur(Length), + Brightness(CSSFloat), + Contrast(CSSFloat), + Grayscale(CSSFloat), + HueRotate(Angle), + Invert(CSSFloat), + Opacity(CSSFloat), + Saturate(CSSFloat), + Sepia(CSSFloat), + } pub mod computed_value { - use values::specified::Angle; + use util::geometry::Au; use values::CSSFloat; - use cssparser::ToCss; - use text_writer::{self, TextWriter}; + use values::specified::{Angle}; - // TODO(pcwalton): `blur`, `drop-shadow` #[derive(Clone, PartialEq, Debug)] pub enum Filter { + Blur(Au), Brightness(CSSFloat), Contrast(CSSFloat), Grayscale(CSSFloat), @@ -2721,54 +2737,16 @@ pub mod longhands { Sepia(CSSFloat), } - impl ToCss for Filter { - fn to_css(&self, dest: &mut W) -> text_writer::Result where W: TextWriter { - match *self { - Filter::Brightness(value) => try!(write!(dest, "brightness({})", value)), - Filter::Contrast(value) => try!(write!(dest, "contrast({})", value)), - Filter::Grayscale(value) => try!(write!(dest, "grayscale({})", value)), - Filter::HueRotate(value) => { - try!(dest.write_str("hue-rotate(")); - try!(value.to_css(dest)); - try!(dest.write_str(")")); - } - Filter::Invert(value) => try!(write!(dest, "invert({})", value)), - Filter::Opacity(value) => try!(write!(dest, "opacity({})", value)), - Filter::Saturate(value) => try!(write!(dest, "saturate({})", value)), - Filter::Sepia(value) => try!(write!(dest, "sepia({})", value)), - } - Ok(()) - } - } - #[derive(Clone, PartialEq, Debug)] - pub struct T { - pub filters: Vec, - } - - impl ToCss for T { - fn to_css(&self, dest: &mut W) -> text_writer::Result where W: TextWriter { - let mut iter = self.filters.iter(); - if let Some(filter) = iter.next() { - try!(filter.to_css(dest)); - } else { - try!(dest.write_str("none")); - return Ok(()) - } - for filter in iter { - try!(dest.write_str(" ")); - try!(filter.to_css(dest)); - } - Ok(()) - } - } + pub struct T { pub filters: Vec } impl T { /// Creates a new filter pipeline. #[inline] pub fn new(filters: Vec) -> T { - T { - filters: filters, + T + { + filters: filters, } } @@ -2788,6 +2766,7 @@ pub mod longhands { #[inline] pub fn opacity(&self) -> CSSFloat { let mut opacity = 1.0; + for filter in self.filters.iter() { if let Filter::Opacity(ref opacity_value) = *filter { opacity *= *opacity_value @@ -2798,6 +2777,48 @@ pub mod longhands { } } + impl ToCss for SpecifiedValue { + fn to_css(&self, dest: &mut W) -> text_writer::Result where W: TextWriter { + let mut iter = self.0.iter(); + if let Some(filter) = iter.next() { + try!(filter.to_css(dest)); + } else { + try!(dest.write_str("none")); + return Ok(()) + } + for filter in iter { + try!(dest.write_str(" ")); + try!(filter.to_css(dest)); + } + Ok(()) + } + } + + impl ToCss for SpecifiedFilter { + fn to_css(&self, dest: &mut W) -> text_writer::Result where W: TextWriter { + match *self { + SpecifiedFilter::Blur(value) => { + try!(dest.write_str("blur(")); + try!(value.to_css(dest)); + try!(dest.write_str(")")); + } + SpecifiedFilter::Brightness(value) => try!(write!(dest, "brightness({})", value)), + SpecifiedFilter::Contrast(value) => try!(write!(dest, "contrast({})", value)), + SpecifiedFilter::Grayscale(value) => try!(write!(dest, "grayscale({})", value)), + SpecifiedFilter::HueRotate(value) => { + try!(dest.write_str("hue-rotate(")); + try!(value.to_css(dest)); + try!(dest.write_str(")")); + } + SpecifiedFilter::Invert(value) => try!(write!(dest, "invert({})", value)), + SpecifiedFilter::Opacity(value) => try!(write!(dest, "opacity({})", value)), + SpecifiedFilter::Saturate(value) => try!(write!(dest, "saturate({})", value)), + SpecifiedFilter::Sepia(value) => try!(write!(dest, "sepia({})", value)), + } + Ok(()) + } + } + #[inline] pub fn get_initial_value() -> computed_value::T { computed_value::T::new(Vec::new()) @@ -2806,27 +2827,28 @@ pub mod longhands { pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result { let mut filters = Vec::new(); if input.try(|input| input.expect_ident_matching("none")).is_ok() { - return Ok(SpecifiedValue::new(filters)) + return Ok(SpecifiedValue(filters)) } loop { if let Ok(function_name) = input.try(|input| input.expect_function()) { filters.push(try!(input.parse_nested_block(|input| { match_ignore_ascii_case! { function_name, - "brightness" => parse_factor(input).map(Filter::Brightness), - "contrast" => parse_factor(input).map(Filter::Contrast), - "grayscale" => parse_factor(input).map(Filter::Grayscale), - "hue-rotate" => Angle::parse(input).map(Filter::HueRotate), - "invert" => parse_factor(input).map(Filter::Invert), - "opacity" => parse_factor(input).map(Filter::Opacity), - "saturate" => parse_factor(input).map(Filter::Saturate), - "sepia" => parse_factor(input).map(Filter::Sepia) + "blur" => specified::Length::parse_non_negative(input).map(SpecifiedFilter::Blur), + "brightness" => parse_factor(input).map(SpecifiedFilter::Brightness), + "contrast" => parse_factor(input).map(SpecifiedFilter::Contrast), + "grayscale" => parse_factor(input).map(SpecifiedFilter::Grayscale), + "hue-rotate" => Angle::parse(input).map(SpecifiedFilter::HueRotate), + "invert" => parse_factor(input).map(SpecifiedFilter::Invert), + "opacity" => parse_factor(input).map(SpecifiedFilter::Opacity), + "saturate" => parse_factor(input).map(SpecifiedFilter::Saturate), + "sepia" => parse_factor(input).map(SpecifiedFilter::Sepia) _ => Err(()) } }))); } else if filters.is_empty() { return Err(()) } else { - return Ok(SpecifiedValue::new(filters)) + return Ok(SpecifiedValue(filters)) } } } @@ -2839,6 +2861,26 @@ pub mod longhands { _ => Err(()) } } + + impl ToComputedValue for SpecifiedValue { + type ComputedValue = computed_value::T; + + fn to_computed_value(&self, context: &computed::Context) -> computed_value::T { + computed_value::T{ filters: self.0.iter().map(|value| { + match value { + &SpecifiedFilter::Blur(factor) => computed_value::Filter::Blur(factor.to_computed_value(context)), + &SpecifiedFilter::Brightness(factor) => computed_value::Filter::Brightness(factor), + &SpecifiedFilter::Contrast(factor) => computed_value::Filter::Contrast(factor), + &SpecifiedFilter::Grayscale(factor) => computed_value::Filter::Grayscale(factor), + &SpecifiedFilter::HueRotate(factor) => computed_value::Filter::HueRotate(factor), + &SpecifiedFilter::Invert(factor) => computed_value::Filter::Invert(factor), + &SpecifiedFilter::Opacity(factor) => computed_value::Filter::Opacity(factor), + &SpecifiedFilter::Saturate(factor) => computed_value::Filter::Saturate(factor), + &SpecifiedFilter::Sepia(factor) => computed_value::Filter::Sepia(factor), + } + }).collect() } + } + } <%self:longhand name="transform"> diff --git a/tests/ref/basic.list b/tests/ref/basic.list index 31186763f0d7..28a31560623a 100644 --- a/tests/ref/basic.list +++ b/tests/ref/basic.list @@ -73,6 +73,7 @@ flaky_cpu == append_style_a.html append_style_b.html != block_image.html noteq_500x300_white.html == block_replaced_content_a.html block_replaced_content_ref.html == block_replaced_content_b.html block_replaced_content_ref.html +== blur_a.html blur_ref.html != border_black_groove.html border_black_solid.html != border_black_ridge.html border_black_groove.html != border_black_ridge.html border_black_solid.html diff --git a/tests/ref/blur_a.html b/tests/ref/blur_a.html new file mode 100644 index 000000000000..937b21445af6 --- /dev/null +++ b/tests/ref/blur_a.html @@ -0,0 +1,29 @@ + + + + + + +
+
+
+ + diff --git a/tests/ref/blur_ref.html b/tests/ref/blur_ref.html new file mode 100644 index 000000000000..1f1996f785fc --- /dev/null +++ b/tests/ref/blur_ref.html @@ -0,0 +1,17 @@ + + + + + + +
+
+ +