diff options
-rw-r--r-- | components/gfx/display_list/mod.rs | 13 | ||||
-rw-r--r-- | components/gfx/paint_context.rs | 53 | ||||
-rw-r--r-- | components/layout/display_list_builder.rs | 9 | ||||
-rw-r--r-- | components/layout/fragment.rs | 9 | ||||
-rw-r--r-- | components/layout/layout_task.rs | 3 | ||||
-rw-r--r-- | components/layout/text.rs | 8 | ||||
-rw-r--r-- | components/style/properties/mod.rs.mako | 3 | ||||
-rw-r--r-- | tests/ref/basic.list | 1 | ||||
-rw-r--r-- | tests/ref/mix_blend_mode_a.html | 53 | ||||
-rw-r--r-- | tests/ref/mix_blend_mode_ref.html | 52 |
10 files changed, 181 insertions, 23 deletions
diff --git a/components/gfx/display_list/mod.rs b/components/gfx/display_list/mod.rs index 640eb770524..6ede4546b6b 100644 --- a/components/gfx/display_list/mod.rs +++ b/components/gfx/display_list/mod.rs @@ -41,7 +41,7 @@ use std::fmt; use std::slice::Items; use std::sync::Arc; use style::ComputedValues; -use style::computed_values::{border_style, cursor, filter, pointer_events}; +use style::computed_values::{border_style, cursor, filter, mix_blend_mode, pointer_events}; // It seems cleaner to have layout code not mention Azure directly, so let's just reexport this for // layout to use. @@ -165,6 +165,8 @@ pub struct StackingContext { pub z_index: i32, /// CSS filters to be applied to this stacking context (including opacity). pub filters: filter::T, + /// The blend mode with which this stacking context blends with its backdrop. + pub blend_mode: mix_blend_mode::T, } impl StackingContext { @@ -175,6 +177,7 @@ impl StackingContext { overflow: &Rect<Au>, z_index: i32, filters: filter::T, + blend_mode: mix_blend_mode::T, layer: Option<Arc<PaintLayer>>) -> StackingContext { StackingContext { @@ -184,6 +187,7 @@ impl StackingContext { overflow: *overflow, z_index: z_index, filters: filters, + blend_mode: blend_mode, } } @@ -194,7 +198,7 @@ impl StackingContext { transform: &Matrix2D<AzFloat>, clip_rect: Option<&Rect<Au>>) { let temporary_draw_target = - paint_context.get_or_create_temporary_draw_target(&self.filters); + paint_context.get_or_create_temporary_draw_target(&self.filters, self.blend_mode); { let mut paint_subcontext = PaintContext { draw_target: temporary_draw_target.clone(), @@ -307,7 +311,8 @@ impl StackingContext { } paint_context.draw_temporary_draw_target_if_necessary(&temporary_draw_target, - &self.filters) + &self.filters, + self.blend_mode) } /// Translate the given tile rect into the coordinate system of a child stacking context. @@ -849,7 +854,7 @@ impl DisplayItem { bounds.origin.y = bounds.origin.y + y_offset; bounds.size = image_item.stretch_size; - paint_context.draw_image(bounds, image_item.image.clone()); + paint_context.draw_image(&bounds, image_item.image.clone()); x_offset = x_offset + image_item.stretch_size.width; } diff --git a/components/gfx/paint_context.rs b/components/gfx/paint_context.rs index 5f2a94ad4f5..4866a7aed00 100644 --- a/components/gfx/paint_context.rs +++ b/components/gfx/paint_context.rs @@ -34,7 +34,7 @@ use std::f32; use std::mem; use std::num::{Float, FloatMath}; use std::ptr; -use style::computed_values::{border_style, filter}; +use style::computed_values::{border_style, filter, mix_blend_mode}; use std::sync::Arc; use text::TextRun; use text::glyph::CharIndex; @@ -124,7 +124,7 @@ impl<'a> PaintContext<'a> { self.draw_target.pop_clip(); } - pub fn draw_image(&self, bounds: Rect<Au>, image: Arc<Box<Image>>) { + pub fn draw_image(&self, bounds: &Rect<Au>, image: Arc<Box<Image>>) { let size = Size2D(image.width as i32, image.height as i32); let (pixel_width, pixels, source_format) = match image.pixels { PixelsByColorType::RGBA8(ref pixels) => (4, pixels.as_slice(), SurfaceFormat::B8G8R8A8), @@ -864,15 +864,18 @@ impl<'a> PaintContext<'a> { &end_point.to_azure_point(), stops, &Matrix2D::identity()); - self.draw_target.fill_rect(&bounds.to_azure_rect(), PatternRef::LinearGradient(&pattern), None); } - pub fn get_or_create_temporary_draw_target(&mut self, filters: &filter::T) -> DrawTarget { + pub fn get_or_create_temporary_draw_target(&mut self, + filters: &filter::T, + blend_mode: mix_blend_mode::T) + -> DrawTarget { // Determine if we need a temporary draw target. - if !filters::temporary_draw_target_needed_for_style_filters(filters) { + if !filters::temporary_draw_target_needed_for_style_filters(filters) && + blend_mode == mix_blend_mode::T::normal { // Reuse the draw target, but remove the transient clip. If we don't do the latter, // we'll be in a state whereby the paint subcontext thinks it has no transient clip // (see `StackingContext::optimize_and_draw_into_context`) but it actually does, @@ -896,7 +899,8 @@ impl<'a> PaintContext<'a> { /// after doing all the painting, and the temporary draw target must not be used afterward. pub fn draw_temporary_draw_target_if_necessary(&mut self, temporary_draw_target: &DrawTarget, - filters: &filter::T) { + filters: &filter::T, + blend_mode: mix_blend_mode::T) { if (*temporary_draw_target) == self.draw_target { // We're directly painting to the surface; nothing to do. return @@ -914,11 +918,9 @@ impl<'a> PaintContext<'a> { // Perform the blit operation. let rect = Rect(Point2D(0.0, 0.0), self.draw_target.get_size().to_azure_size()); - let draw_options = DrawOptions::new(opacity, 0); - self.draw_target.draw_filter(&filter_node, - &rect, - &rect.origin, - draw_options); + 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); self.draw_target.set_transform(&old_transform); } @@ -1261,3 +1263,32 @@ impl DrawTargetExtensions for DrawTarget { } } +/// Converts a CSS blend mode (per CSS-COMPOSITING) to an Azure `CompositionOp`. +trait ToAzureCompositionOp { + /// Converts a CSS blend mode (per CSS-COMPOSITING) to an Azure `CompositionOp`. + fn to_azure_composition_op(&self) -> CompositionOp; +} + +impl ToAzureCompositionOp for mix_blend_mode::T { + fn to_azure_composition_op(&self) -> CompositionOp { + match *self { + mix_blend_mode::T::normal => CompositionOp::Over, + mix_blend_mode::T::multiply => CompositionOp::Multiply, + mix_blend_mode::T::screen => CompositionOp::Screen, + mix_blend_mode::T::overlay => CompositionOp::Overlay, + mix_blend_mode::T::darken => CompositionOp::Darken, + mix_blend_mode::T::lighten => CompositionOp::Lighten, + mix_blend_mode::T::color_dodge => CompositionOp::ColorDodge, + mix_blend_mode::T::color_burn => CompositionOp::ColorBurn, + mix_blend_mode::T::hard_light => CompositionOp::HardLight, + mix_blend_mode::T::soft_light => CompositionOp::SoftLight, + mix_blend_mode::T::difference => CompositionOp::Difference, + mix_blend_mode::T::exclusion => CompositionOp::Exclusion, + mix_blend_mode::T::hue => CompositionOp::Hue, + mix_blend_mode::T::saturation => CompositionOp::Saturation, + mix_blend_mode::T::color => CompositionOp::Color, + mix_blend_mode::T::luminosity => CompositionOp::Luminosity, + } + } +} + diff --git a/components/layout/display_list_builder.rs b/components/layout/display_list_builder.rs index c394edb4a51..70b8b409edb 100644 --- a/components/layout/display_list_builder.rs +++ b/components/layout/display_list_builder.rs @@ -371,7 +371,9 @@ impl FragmentDisplayListBuilding for Fragment { // Create the image display item. display_list.push(DisplayItem::ImageClass(box ImageDisplayItem { base: BaseDisplayItem::new(bounds, - DisplayItemMetadata::new(self.node, style, Cursor::DefaultCursor), + DisplayItemMetadata::new(self.node, + style, + Cursor::DefaultCursor), clip), image: image.clone(), stretch_size: Size2D(Au::from_px(image.width as int), @@ -481,7 +483,9 @@ impl FragmentDisplayListBuilding for Fragment { let gradient_display_item = DisplayItem::GradientClass(box GradientDisplayItem { base: BaseDisplayItem::new(*absolute_bounds, - DisplayItemMetadata::new(self.node, style, Cursor::DefaultCursor), + DisplayItemMetadata::new(self.node, + style, + Cursor::DefaultCursor), clip), start_point: center - delta, end_point: center + delta, @@ -1194,6 +1198,7 @@ impl BlockFlowDisplayListBuilding for BlockFlow { &overflow, self.fragment.style().get_box().z_index.number_or_zero(), filters, + self.fragment.style().get_effects().mix_blend_mode, layer)) } } diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs index 6217527bf6b..521b000dfcf 100644 --- a/components/layout/fragment.rs +++ b/components/layout/fragment.rs @@ -44,9 +44,9 @@ use std::sync::{Arc, Mutex}; use string_cache::Atom; use style::{ComputedValues, TElement, TNode, cascade_anonymous}; use style::computed_values::{LengthOrPercentage, LengthOrPercentageOrAuto}; -use style::computed_values::{LengthOrPercentageOrNone}; -use style::computed_values::{clear, overflow_wrap, position, text_align, text_decoration}; -use style::computed_values::{vertical_align, white_space, word_break}; +use style::computed_values::{LengthOrPercentageOrNone, clear, mix_blend_mode, overflow_wrap}; +use style::computed_values::{position, text_align, text_decoration, vertical_align, white_space}; +use style::computed_values::{word_break}; use url::Url; /// Fragments (`struct Fragment`) are the leaves of the layout tree. They cannot position @@ -1747,6 +1747,9 @@ impl Fragment { if !self.style().get_effects().filter.is_empty() { return true } + if self.style().get_effects().mix_blend_mode != mix_blend_mode::T::normal { + return true + } match self.style().get_box().position { position::T::absolute | position::T::fixed => { // FIXME(pcwalton): This should only establish a new stacking context when diff --git a/components/layout/layout_task.rs b/components/layout/layout_task.rs index 167e1d4a846..b8c2385a557 100644 --- a/components/layout/layout_task.rs +++ b/components/layout/layout_task.rs @@ -63,7 +63,7 @@ use std::cell::Cell; use std::comm::{channel, Sender, Receiver, Select}; use std::mem; use std::ptr; -use style::computed_values::filter; +use style::computed_values::{filter, mix_blend_mode}; use style::{StylesheetOrigin, Stylesheet, Stylist, TNode, iter_font_face_rules}; use style::{MediaType, Device}; use std::sync::{Arc, Mutex, MutexGuard}; @@ -698,6 +698,7 @@ impl LayoutTask { &origin, 0, filter::T::new(Vec::new()), + mix_blend_mode::T::normal, Some(paint_layer))); rw_data.stacking_context = Some(stacking_context.clone()); diff --git a/components/layout/text.rs b/components/layout/text.rs index ae4ad5ec0de..f57aee50f7b 100644 --- a/components/layout/text.rs +++ b/components/layout/text.rs @@ -116,7 +116,9 @@ impl TextRunScanner { let inherited_text_style = in_fragment.style().get_inheritedtext(); fontgroup = font_context.get_layout_font_group_for_style(font_style); compression = match in_fragment.white_space() { - white_space::T::normal | white_space::T::nowrap => CompressionMode::CompressWhitespaceNewline, + white_space::T::normal | white_space::T::nowrap => { + CompressionMode::CompressWhitespaceNewline + } white_space::T::pre => CompressionMode::CompressNone, }; text_transform = inherited_text_style.text_transform; @@ -129,7 +131,9 @@ impl TextRunScanner { let mut run_text = String::new(); for in_fragment in self.clump.iter() { let in_fragment = match in_fragment.specific { - SpecificFragmentInfo::UnscannedText(ref text_fragment_info) => &text_fragment_info.text, + SpecificFragmentInfo::UnscannedText(ref text_fragment_info) => { + &text_fragment_info.text + } _ => panic!("Expected an unscanned text fragment!"), }; diff --git a/components/style/properties/mod.rs.mako b/components/style/properties/mod.rs.mako index 638d86aff47..29f4c17b8a8 100644 --- a/components/style/properties/mod.rs.mako +++ b/components/style/properties/mod.rs.mako @@ -1980,6 +1980,9 @@ pub mod longhands { } } </%self:longhand> + + ${single_keyword("mix-blend-mode", + "normal multiply screen overlay darken lighten color-dodge color-burn hard-light soft-light difference exclusion hue saturation color luminosity")} } diff --git a/tests/ref/basic.list b/tests/ref/basic.list index 94f35425363..60f5e65171f 100644 --- a/tests/ref/basic.list +++ b/tests/ref/basic.list @@ -228,3 +228,4 @@ fragment=top != ../html/acid2.html acid2_ref.html == outline_offset_a.html outline_offset_ref.html == filter_opacity_a.html filter_opacity_ref.html == filter_sepia_a.html filter_sepia_ref.html +== mix_blend_mode_a.html mix_blend_mode_ref.html diff --git a/tests/ref/mix_blend_mode_a.html b/tests/ref/mix_blend_mode_a.html new file mode 100644 index 00000000000..f2c49230625 --- /dev/null +++ b/tests/ref/mix_blend_mode_a.html @@ -0,0 +1,53 @@ +<!DOCTYPE html> +<html> +<head> +<style> +html { + background: #ffffff; +} +section { + position: absolute; + left: 0; +} +#a { + top: 0; + mix-blend-mode: difference; +} +#b { + top: 100px; + mix-blend-mode: exclusion; +} +div { + position: absolute; + top: 0; + width: 100px; + height: 100px; +} +.red { + left: 0; + background-color: #ff0000; +} +.green { + left: 100px; + background: #00ff00; +} +.blue { + left: 200px; + background: #0000ff; +} +</style> +</head> +<body> +<section id=a> +<div class=red></div> +<div class=green></div> +<div class=blue></div> +</section> +<section id=b> +<div class=red></div> +<div class=green></div> +<div class=blue></div> +</section> +</body> +</html> + diff --git a/tests/ref/mix_blend_mode_ref.html b/tests/ref/mix_blend_mode_ref.html new file mode 100644 index 00000000000..d91d4bcef46 --- /dev/null +++ b/tests/ref/mix_blend_mode_ref.html @@ -0,0 +1,52 @@ +<!DOCTYPE html> +<html> +<head> +<style> +html { + background: #ffffff; +} +section { + position: absolute; + left: 0; +} +#a { + top: 0; +} +#b { + top: 100px; +} +div { + position: absolute; + top: 0; + width: 100px; + height: 100px; +} +.red { + left: 0; + background-color: #00ffff; +} +.green { + left: 100px; + background: #ff00ff; +} +.blue { + left: 200px; + background: #ffff00; +} +</style> +</head> +<body> +<section id=a> +<div class=red></div> +<div class=green></div> +<div class=blue></div> +</section> +<section id=b> +<div class=red></div> +<div class=green></div> +<div class=blue></div> +</section> +</body> +</html> + + |