diff --git a/components/canvas/canvas_paint_task.rs b/components/canvas/canvas_paint_task.rs index 0e4ee649a5b..2f0c36fd40e 100644 --- a/components/canvas/canvas_paint_task.rs +++ b/components/canvas/canvas_paint_task.rs @@ -56,8 +56,7 @@ impl CanvasPaintTask { fn fill_rect(&self, rect: &Rect) { let drawopts = DrawOptions::new(1.0, 0); - self.drawtarget.fill_rect(rect, PatternRef::ColorPatternRef(&self.fill_color), - Some(&drawopts)); + self.drawtarget.fill_rect(rect, PatternRef::Color(&self.fill_color), Some(&drawopts)); } fn clear_rect(&self, rect: &Rect) { @@ -70,7 +69,7 @@ impl CanvasPaintTask { } fn create(size: Size2D) -> DrawTarget { - DrawTarget::new(BackendType::SkiaBackend, size, SurfaceFormat::B8G8R8A8) + DrawTarget::new(BackendType::Skia, size, SurfaceFormat::B8G8R8A8) } fn recreate(&mut self, size: Size2D) { diff --git a/components/gfx/display_list/mod.rs b/components/gfx/display_list/mod.rs index 6d524a85cd2..640eb770524 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, pointer_events}; +use style::computed_values::{border_style, cursor, filter, pointer_events}; // It seems cleaner to have layout code not mention Azure directly, so let's just reexport this for // layout to use. @@ -163,8 +163,8 @@ pub struct StackingContext { pub overflow: Rect, /// The `z-index` for this stacking context. pub z_index: i32, - /// The opacity of this stacking context. - pub opacity: AzFloat, + /// CSS filters to be applied to this stacking context (including opacity). + pub filters: filter::T, } impl StackingContext { @@ -174,7 +174,7 @@ impl StackingContext { bounds: &Rect, overflow: &Rect, z_index: i32, - opacity: AzFloat, + filters: filter::T, layer: Option>) -> StackingContext { StackingContext { @@ -183,7 +183,7 @@ impl StackingContext { bounds: *bounds, overflow: *overflow, z_index: z_index, - opacity: opacity, + filters: filters, } } @@ -194,7 +194,7 @@ impl StackingContext { transform: &Matrix2D, clip_rect: Option<&Rect>) { let temporary_draw_target = - paint_context.get_or_create_temporary_draw_target(self.opacity); + paint_context.get_or_create_temporary_draw_target(&self.filters); { let mut paint_subcontext = PaintContext { draw_target: temporary_draw_target.clone(), @@ -306,7 +306,8 @@ impl StackingContext { paint_subcontext.draw_target.set_transform(&old_transform) } - paint_context.draw_temporary_draw_target_if_necessary(&temporary_draw_target, self.opacity) + paint_context.draw_temporary_draw_target_if_necessary(&temporary_draw_target, + &self.filters) } /// Translate the given tile rect into the coordinate system of a child stacking context. diff --git a/components/gfx/filters.rs b/components/gfx/filters.rs new file mode 100644 index 00000000000..a1cc25d2bd0 --- /dev/null +++ b/components/gfx/filters.rs @@ -0,0 +1,198 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! CSS and SVG filter support. + +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 std::num::FloatMath; +use style::computed_values::filter; + +/// 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) + -> (FilterNode, AzFloat) { + let mut opacity = 1.0; + let mut filter = draw_target.create_filter(FilterType::Composite); + filter.set_input(CompositeInput, &temporary_draw_target.snapshot()); + for style_filter in style_filters.filters.iter() { + match *style_filter { + filter::Filter::HueRotate(angle) => { + let hue_rotate = draw_target.create_filter(FilterType::ColorMatrix); + let matrix = self::hue_rotate(angle.radians() as AzFloat); + hue_rotate.set_attribute(ColorMatrixAttribute::Matrix(matrix)); + hue_rotate.set_input(ColorMatrixInput, &filter); + filter = hue_rotate + } + filter::Filter::Opacity(opacity_value) => opacity *= opacity_value as AzFloat, + filter::Filter::Saturate(amount) => { + let saturate = draw_target.create_filter(FilterType::ColorMatrix); + let matrix = self::saturate(amount as AzFloat); + saturate.set_attribute(ColorMatrixAttribute::Matrix(matrix)); + saturate.set_input(ColorMatrixInput, &filter); + filter = saturate + } + filter::Filter::Sepia(amount) => { + let sepia = draw_target.create_filter(FilterType::ColorMatrix); + let matrix = self::sepia(amount as AzFloat); + sepia.set_attribute(ColorMatrixAttribute::Matrix(matrix)); + sepia.set_input(ColorMatrixInput, &filter); + filter = sepia + } + filter::Filter::Grayscale(amount) => { + let amount = amount as AzFloat; + let grayscale = draw_target.create_filter(FilterType::ColorMatrix); + grayscale.set_attribute(ColorMatrixAttribute::Matrix(self::grayscale(amount))); + grayscale.set_input(ColorMatrixInput, &filter); + filter = grayscale + } + filter::Filter::Invert(amount) => { + let amount = amount as AzFloat; + let invert = draw_target.create_filter(FilterType::TableTransfer); + invert.set_attribute(TableTransferAttribute::DisableR(false)); + invert.set_attribute(TableTransferAttribute::DisableG(false)); + invert.set_attribute(TableTransferAttribute::DisableB(false)); + invert.set_attribute(TableTransferAttribute::TableR(&[1.0, amount - 1.0])); + invert.set_attribute(TableTransferAttribute::TableG(&[1.0, amount - 1.0])); + invert.set_attribute(TableTransferAttribute::TableB(&[1.0, amount - 1.0])); + invert.set_input(TableTransferInput, &filter); + filter = invert + } + filter::Filter::Brightness(amount) => { + let amount = amount as AzFloat; + let brightness = draw_target.create_filter(FilterType::LinearTransfer); + brightness.set_attribute(LinearTransferAttribute::DisableR(false)); + brightness.set_attribute(LinearTransferAttribute::DisableG(false)); + brightness.set_attribute(LinearTransferAttribute::DisableB(false)); + brightness.set_attribute(LinearTransferAttribute::SlopeR(amount)); + brightness.set_attribute(LinearTransferAttribute::SlopeG(amount)); + brightness.set_attribute(LinearTransferAttribute::SlopeB(amount)); + brightness.set_input(LinearTransferInput, &filter); + filter = brightness + } + filter::Filter::Contrast(amount) => { + let amount = amount as AzFloat; + let contrast = draw_target.create_filter(FilterType::LinearTransfer); + contrast.set_attribute(LinearTransferAttribute::DisableR(false)); + contrast.set_attribute(LinearTransferAttribute::DisableG(false)); + contrast.set_attribute(LinearTransferAttribute::DisableB(false)); + contrast.set_attribute(LinearTransferAttribute::SlopeR(amount)); + contrast.set_attribute(LinearTransferAttribute::SlopeG(amount)); + contrast.set_attribute(LinearTransferAttribute::SlopeB(amount)); + contrast.set_attribute(LinearTransferAttribute::InterceptR(-0.5 * amount + 0.5)); + contrast.set_attribute(LinearTransferAttribute::InterceptG(-0.5 * amount + 0.5)); + contrast.set_attribute(LinearTransferAttribute::InterceptB(-0.5 * amount + 0.5)); + contrast.set_input(LinearTransferInput, &filter); + filter = contrast + } + } + } + (filter, opacity) +} + +/// Determines if we need a temporary draw target for the given set of filters. +pub fn temporary_draw_target_needed_for_style_filters(filters: &filter::T) -> bool { + for filter in filters.filters.iter() { + match *filter { + filter::Filter::Opacity(value) if value == 1.0 => continue, + _ => return true, + } + } + false +} + +/// Creates a grayscale 5x4 color matrix per CSS-FILTERS § 12.1.1. +fn grayscale(amount: AzFloat) -> Matrix5x4 { + Matrix5x4 { + m11: 0.2126 + 0.7874 * (1.0 - amount), + m21: 0.7152 - 0.7152 * (1.0 - amount), + m31: 0.0722 - 0.0722 * (1.0 - amount), + m41: 0.0, + m51: 0.0, + m12: 0.2126 - 0.2126 * (1.0 - amount), + m22: 0.7152 + 0.2848 * (1.0 - amount), + m32: 0.0722 - 0.0722 * (1.0 - amount), + m42: 0.0, + m52: 0.0, + m13: 0.2126 - 0.2126 * (1.0 - amount), + m23: 0.7152 - 0.7152 * (1.0 - amount), + m33: 0.0722 + 0.9278 * (1.0 - amount), + m43: 0.0, + m53: 0.0, + m14: 0.0, m24: 0.0, m34: 0.0, m44: 1.0, m54: 0.0, + } +} + +/// Creates a 5x4 hue rotation color matrix per CSS-FILTERS § 8.5. +fn hue_rotate(angle: AzFloat) -> Matrix5x4 { + let (c, s) = (angle.cos(), angle.sin()); + Matrix5x4 { + m11: 0.213 + c * 0.787 + s * -0.213, + m21: 0.715 + c * -0.715 + s * -0.715, + m31: 0.072 + c * -0.072 + s * 0.928, + m41: 0.0, + m51: 0.0, + m12: 0.213 + c * -0.213 + s * 0.143, + m22: 0.715 + c * 0.285 + s * 0.140, + m32: 0.072 + c * -0.072 + s * -0.283, + m42: 0.0, + m52: 0.0, + m13: 0.213 + c * -0.213 + s * -0.787, + m23: 0.715 + c * -0.715 + s * 0.715, + m33: 0.072 + c * 0.928 + s * 0.072, + m43: 0.0, + m53: 0.0, + m14: 0.0, m24: 0.0, m34: 0.0, m44: 1.0, m54: 0.0, + } +} + +/// Creates a 5x4 saturation color matrix per CSS-FILTERS § 8.5. +fn saturate(amount: AzFloat) -> Matrix5x4 { + Matrix5x4 { + m11: 0.213 + 0.787 * amount, + m21: 0.715 - 0.715 * amount, + m31: 0.072 - 0.072 * amount, + m41: 0.0, + m51: 0.0, + m12: 0.213 - 0.213 * amount, + m22: 0.715 + 0.285 * amount, + m32: 0.072 - 0.072 * amount, + m42: 0.0, + m52: 0.0, + m13: 0.213 - 0.213 * amount, + m23: 0.715 - 0.715 * amount, + m33: 0.072 + 0.928 * amount, + m43: 0.0, + m53: 0.0, + m14: 0.0, m24: 0.0, m34: 0.0, m44: 1.0, m54: 0.0, + } +} + +/// Creates a sepia 5x4 color matrix per CSS-FILTERS § 12.1.1. +fn sepia(amount: AzFloat) -> Matrix5x4 { + Matrix5x4 { + m11: 0.393 + 0.607 * (1.0 - amount), + m21: 0.769 - 0.769 * (1.0 - amount), + m31: 0.189 - 0.189 * (1.0 - amount), + m41: 0.0, + m51: 0.0, + m12: 0.349 - 0.349 * (1.0 - amount), + m22: 0.686 + 0.314 * (1.0 - amount), + m32: 0.168 - 0.168 * (1.0 - amount), + m42: 0.0, + m52: 0.0, + m13: 0.272 - 0.272 * (1.0 - amount), + m23: 0.534 - 0.534 * (1.0 - amount), + m33: 0.131 + 0.869 * (1.0 - amount), + m43: 0.0, + m53: 0.0, + m14: 0.0, m24: 0.0, m34: 0.0, m44: 1.0, m54: 0.0, + } +} + diff --git a/components/gfx/font_context.rs b/components/gfx/font_context.rs index f6d715be693..47764026458 100644 --- a/components/gfx/font_context.rs +++ b/components/gfx/font_context.rs @@ -30,14 +30,14 @@ use azure::scaled_font::FontInfo; #[cfg(any(target_os="linux", target_os = "android"))] fn create_scaled_font(template: &Arc, pt_size: Au) -> ScaledFont { - ScaledFont::new(BackendType::SkiaBackend, FontInfo::FontData(&template.bytes), + ScaledFont::new(BackendType::Skia, FontInfo::FontData(&template.bytes), pt_size.to_subpx() as AzFloat) } #[cfg(target_os="macos")] fn create_scaled_font(template: &Arc, pt_size: Au) -> ScaledFont { let cgfont = template.ctfont.as_ref().unwrap().copy_to_CGFont(); - ScaledFont::new(BackendType::SkiaBackend, &cgfont, pt_size.to_subpx() as AzFloat) + ScaledFont::new(BackendType::Skia, &cgfont, pt_size.to_subpx() as AzFloat) } static SMALL_CAPS_SCALE_FACTOR: f64 = 0.8; // Matches FireFox (see gfxFont.h) diff --git a/components/gfx/lib.rs b/components/gfx/lib.rs index c4076ea70e4..d90a7185d8a 100644 --- a/components/gfx/lib.rs +++ b/components/gfx/lib.rs @@ -68,6 +68,7 @@ pub mod font_template; // Misc. mod buffer_map; +mod filters; // Platform-specific implementations. #[path="platform/mod.rs"] diff --git a/components/gfx/paint_context.rs b/components/gfx/paint_context.rs index d6fcd6d1475..5f2a94ad4f5 100644 --- a/components/gfx/paint_context.rs +++ b/components/gfx/paint_context.rs @@ -5,16 +5,17 @@ //! Painting of display lists using Moz2D/Azure. use azure::azure::AzIntSize; -use azure::azure_hl::{SurfaceFormat, Color, ColorPattern, DrawOptions}; -use azure::azure_hl::{DrawSurfaceOptions, DrawTarget, ExtendMode, FilterType}; +use azure::azure_hl::{Color, ColorPattern}; +use azure::azure_hl::{DrawOptions, DrawSurfaceOptions, DrawTarget, ExtendMode, FilterType}; use azure::azure_hl::{GaussianBlurInput, GradientStop, Filter, LinearGradientPattern}; use azure::azure_hl::{PatternRef, Path, PathBuilder, CompositionOp}; -use azure::azure_hl::{GaussianBlurAttribute, StrokeOptions}; +use azure::azure_hl::{GaussianBlurAttribute, StrokeOptions, SurfaceFormat}; use azure::scaled_font::ScaledFont; use azure::{AZ_CAP_BUTT, AzFloat, struct__AzDrawOptions, struct__AzGlyph}; use azure::{struct__AzGlyphBuffer, struct__AzPoint, AzDrawTargetFillGlyphs}; use display_list::TextOrientation::{SidewaysLeft, SidewaysRight, Upright}; use display_list::{BOX_SHADOW_INFLATION_FACTOR, BorderRadii, ClippingRegion, TextDisplayItem}; +use filters; use font_context::FontContext; use geom::matrix2d::Matrix2D; use geom::point::Point2D; @@ -33,7 +34,7 @@ use std::f32; use std::mem; use std::num::{Float, FloatMath}; use std::ptr; -use style::computed_values::border_style; +use style::computed_values::{border_style, filter}; use std::sync::Arc; use text::TextRun; use text::glyph::CharIndex; @@ -75,7 +76,7 @@ impl<'a> PaintContext<'a> { pub fn draw_solid_color(&self, bounds: &Rect, color: Color) { self.draw_target.make_current(); self.draw_target.fill_rect(&bounds.to_azure_rect(), - PatternRef::ColorPatternRef(&ColorPattern::new(color)), + PatternRef::Color(&ColorPattern::new(color)), None); } @@ -158,10 +159,9 @@ impl<'a> PaintContext<'a> { Size2D(self.screen_rect.size.width as AzFloat, self.screen_rect.size.height as AzFloat)); let mut draw_options = DrawOptions::new(1.0, 0); - draw_options.set_composition_op(CompositionOp::SourceOp); + draw_options.set_composition_op(CompositionOp::Source); self.draw_target.make_current(); - self.draw_target.fill_rect(&rect, PatternRef::ColorPatternRef(&pattern), - Some(&draw_options)); + self.draw_target.fill_rect(&rect, PatternRef::Color(&pattern), Some(&draw_options)); } fn draw_border_segment(&self, @@ -859,19 +859,20 @@ impl<'a> PaintContext<'a> { stops: &[GradientStop]) { self.draw_target.make_current(); - let stops = self.draw_target.create_gradient_stops(stops, ExtendMode::ExtendClamp); + let stops = self.draw_target.create_gradient_stops(stops, ExtendMode::Clamp); let pattern = LinearGradientPattern::new(&start_point.to_azure_point(), &end_point.to_azure_point(), stops, &Matrix2D::identity()); self.draw_target.fill_rect(&bounds.to_azure_rect(), - PatternRef::LinearGradientPatternRef(&pattern), + PatternRef::LinearGradient(&pattern), None); } - pub fn get_or_create_temporary_draw_target(&mut self, opacity: AzFloat) -> DrawTarget { - if opacity == 1.0 { + pub fn get_or_create_temporary_draw_target(&mut self, filters: &filter::T) -> DrawTarget { + // Determine if we need a temporary draw target. + if !filters::temporary_draw_target_needed_for_style_filters(filters) { // 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, @@ -883,10 +884,7 @@ 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 { - width: size.width, - height: size.height, - }; + let size = Size2D(size.width, size.height); let temporary_draw_target = self.draw_target.create_similar_draw_target(&size, self.draw_target.get_format()); @@ -898,24 +896,29 @@ 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, - opacity: AzFloat) { + filters: &filter::T) { if (*temporary_draw_target) == self.draw_target { // We're directly painting to the surface; nothing to do. return } + // 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()); + + // Create the Azure filter pipeline. + let (filter_node, opacity) = filters::create_filters(&self.draw_target, + temporary_draw_target, + filters); + + // Perform the blit operation. let rect = Rect(Point2D(0.0, 0.0), self.draw_target.get_size().to_azure_size()); - let source_surface = temporary_draw_target.snapshot(); - let draw_surface_options = DrawSurfaceOptions::new(Filter::Linear, true); let draw_options = DrawOptions::new(opacity, 0); - self.draw_target.draw_surface(source_surface, - rect, - rect, - draw_surface_options, - draw_options); + self.draw_target.draw_filter(&filter_node, + &rect, + &rect.origin, + draw_options); self.draw_target.set_transform(&old_transform); } @@ -974,9 +977,9 @@ impl<'a> PaintContext<'a> { if blur_radius > Au(0) { // Go ahead and create the blur now. Despite the name, Azure's notion of `StdDeviation` // describes the blur radius, not the sigma for the Gaussian blur. - let blur_filter = self.draw_target.create_filter(FilterType::GaussianBlurFilterType); - blur_filter.set_attribute(GaussianBlurAttribute::StdDeviationGaussianBlurAttribute( - blur_radius.to_subpx() as AzFloat)); + let blur_filter = self.draw_target.create_filter(FilterType::GaussianBlur); + blur_filter.set_attribute(GaussianBlurAttribute::StdDeviation(blur_radius.to_subpx() as + AzFloat)); blur_filter.set_input(GaussianBlurInput, &temporary_draw_target.snapshot()); // Blit the blur onto the tile. We undo the transforms here because we want to directly @@ -985,7 +988,7 @@ impl<'a> PaintContext<'a> { self.draw_target.set_transform(&Matrix2D::identity()); let temporary_draw_target_size = temporary_draw_target.get_size(); self.draw_target - .draw_filter(blur_filter, + .draw_filter(&blur_filter, &Rect(Point2D(0.0, 0.0), Size2D(temporary_draw_target_size.width as AzFloat, temporary_draw_target_size.height as AzFloat)), diff --git a/components/gfx/paint_task.rs b/components/gfx/paint_task.rs index ddd44f9e724..3c75f3fcbd5 100644 --- a/components/gfx/paint_task.rs +++ b/components/gfx/paint_task.rs @@ -524,11 +524,11 @@ impl WorkerThread { -> DrawTarget { let size = Size2D(tile.screen_rect.size.width as i32, tile.screen_rect.size.height as i32); let draw_target = if !opts::get().gpu_painting { - DrawTarget::new(BackendType::SkiaBackend, size, SurfaceFormat::B8G8R8A8) + DrawTarget::new(BackendType::Skia, size, SurfaceFormat::B8G8R8A8) } else { // FIXME(pcwalton): Cache the components of draw targets (texture color buffer, // paintbuffers) instead of recreating them. - let draw_target = DrawTarget::new_with_fbo(BackendType::SkiaBackend, + let draw_target = DrawTarget::new_with_fbo(BackendType::Skia, native_graphics_context!(self), size, SurfaceFormat::B8G8R8A8); diff --git a/components/layout/display_list_builder.rs b/components/layout/display_list_builder.rs index 804ce001eac..75279b2b7db 100644 --- a/components/layout/display_list_builder.rs +++ b/components/layout/display_list_builder.rs @@ -44,6 +44,7 @@ use std::default::Default; use std::num::FloatMath; use style::computed::{AngleOrCorner, LengthOrPercentage, HorizontalDirection, VerticalDirection}; use style::computed::{Image, LinearGradient}; +use style::computed_values::filter::Filter; use style::computed_values::{background_attachment, background_repeat, border_style, overflow}; use style::computed_values::{position, visibility}; use style::style_structs::Border; @@ -1147,11 +1148,18 @@ impl BlockFlowDisplayListBuilding for BlockFlow { let margin = self.fragment.margin.to_physical(self.base.writing_mode); let overflow = self.base.overflow.translate(&-Point2D(margin.left, Au(0))); + // Create the filter pipeline. + let effects = self.fragment.style().get_effects(); + let mut filters = effects.filter.clone(); + if effects.opacity != 1.0 { + filters.push(Filter::Opacity(effects.opacity)) + } + Arc::new(StackingContext::new(display_list, &border_box, &overflow, self.fragment.style().get_box().z_index.number_or_zero(), - self.fragment.style().get_effects().opacity as f32, + filters, layer)) } } diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs index cf59d0f89aa..a0a3a99ac65 100644 --- a/components/layout/fragment.rs +++ b/components/layout/fragment.rs @@ -1623,6 +1623,9 @@ impl Fragment { if self.style().get_effects().opacity != 1.0 { return true } + if !self.style().get_effects().filter.is_empty() { + 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 f5301c27867..167e1d4a846 100644 --- a/components/layout/layout_task.rs +++ b/components/layout/layout_task.rs @@ -45,8 +45,7 @@ use script_traits::{ConstellationControlMsg, CompositorEvent, OpaqueScriptLayout use script_traits::{ScriptControlChan, UntrustedNodeAddress}; use servo_msg::compositor_msg::ScrollPolicy; use servo_msg::constellation_msg::Msg as ConstellationMsg; -use servo_msg::constellation_msg::{ConstellationChan, Failure, PipelineExitType}; -use servo_msg::constellation_msg::PipelineId; +use servo_msg::constellation_msg::{ConstellationChan, Failure, PipelineExitType, PipelineId}; use servo_net::image_cache_task::{ImageCacheTask, ImageResponseMsg}; use servo_net::local_image_cache::{ImageResponder, LocalImageCache}; use servo_net::resource_task::{ResourceTask, load_bytes_iter}; @@ -57,13 +56,14 @@ use servo_util::opts; use servo_util::smallvec::{SmallVec, SmallVec1, VecLike}; use servo_util::task::spawn_named_with_send_on_failure; use servo_util::task_state; -use servo_util::time::{TimeProfilerCategory, ProfilerMetadata, TimeProfilerChan, TimerMetadataFrameType}; -use servo_util::time::{TimerMetadataReflowType, profile}; +use servo_util::time::{TimeProfilerCategory, ProfilerMetadata, TimeProfilerChan}; +use servo_util::time::{TimerMetadataFrameType, TimerMetadataReflowType, profile}; use servo_util::workqueue::WorkQueue; use std::cell::Cell; use std::comm::{channel, Sender, Receiver, Select}; use std::mem; use std::ptr; +use style::computed_values::filter; use style::{StylesheetOrigin, Stylesheet, Stylist, TNode, iter_font_face_rules}; use style::{MediaType, Device}; use std::sync::{Arc, Mutex, MutexGuard}; @@ -697,7 +697,7 @@ impl LayoutTask { &origin, &origin, 0, - 1.0, + filter::T::new(Vec::new()), Some(paint_layer))); rw_data.stacking_context = Some(stacking_context.clone()); diff --git a/components/servo/Cargo.lock b/components/servo/Cargo.lock index 4d285bbf55e..d3303893e1a 100644 --- a/components/servo/Cargo.lock +++ b/components/servo/Cargo.lock @@ -26,7 +26,7 @@ dependencies = [ [[package]] name = "azure" version = "0.1.0" -source = "git+https://github.com/servo/rust-azure#6884d442052becfcafccc82b17ab316c7831d8d2" +source = "git+https://github.com/servo/rust-azure#e1a91efa633f155da89c6a1dadb0049d847b319d" dependencies = [ "core_foundation 0.1.0 (git+https://github.com/servo/rust-core-foundation)", "core_graphics 0.1.0 (git+https://github.com/servo/rust-core-graphics)", diff --git a/components/style/properties/common_types.rs b/components/style/properties/common_types.rs index 84539d6d059..1ca01d2d504 100644 --- a/components/style/properties/common_types.rs +++ b/components/style/properties/common_types.rs @@ -434,6 +434,15 @@ pub mod specified { Err(()) } } + /// Parses an angle from a token according to CSS-VALUES § 6.1. + pub fn parse(value: &ComponentValue) -> Result { + match *value { + Dimension(ref value, ref unit) => { + Angle::parse_dimension(value.value, unit.as_slice()) + } + _ => Err(()) + } + } } /// Specified values for an image according to CSS-IMAGES. diff --git a/components/style/properties/mod.rs.mako b/components/style/properties/mod.rs.mako index fc4a49d1848..638d86aff47 100644 --- a/components/style/properties/mod.rs.mako +++ b/components/style/properties/mod.rs.mako @@ -1663,19 +1663,24 @@ pub mod longhands { pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context) -> computed_value::T { - value.into_iter().map(|value| { - computed_value::BoxShadow { - offset_x: computed::compute_Au(value.offset_x, context), - offset_y: computed::compute_Au(value.offset_y, context), - blur_radius: computed::compute_Au(value.blur_radius, context), - spread_radius: computed::compute_Au(value.spread_radius, context), - color: value.color.map(|color| color.parsed).unwrap_or(cssparser::Color::CurrentColor), - inset: value.inset, - } - }).collect() + value.into_iter().map(|value| compute_one_box_shadow(value, context)).collect() } - fn parse_one_box_shadow(iter: ParserIter) -> Result { + pub fn compute_one_box_shadow(value: SpecifiedBoxShadow, context: &computed::Context) + -> computed_value::BoxShadow { + computed_value::BoxShadow { + offset_x: computed::compute_Au(value.offset_x, context), + offset_y: computed::compute_Au(value.offset_y, context), + blur_radius: computed::compute_Au(value.blur_radius, context), + spread_radius: computed::compute_Au(value.spread_radius, context), + color: value.color + .map(|color| color.parsed) + .unwrap_or(cssparser::Color::CurrentColor), + inset: value.inset, + } + } + + pub fn parse_one_box_shadow(iter: ParserIter) -> Result { let mut lengths = [specified::Length::Au(Au(0)), ..4]; let mut lengths_parsed = false; let mut color = None; @@ -1833,6 +1838,148 @@ pub mod longhands { } } + + <%self:longhand name="filter"> + pub mod computed_value { + use super::super::{Angle, CSSFloat}; + + // TODO(pcwalton): `blur`, `drop-shadow` + #[deriving(Clone, PartialEq, Show)] + pub enum Filter { + Brightness(CSSFloat), + Contrast(CSSFloat), + Grayscale(CSSFloat), + HueRotate(Angle), + Invert(CSSFloat), + Opacity(CSSFloat), + Saturate(CSSFloat), + Sepia(CSSFloat), + } + + #[deriving(Clone, PartialEq, Show)] + pub struct T { + pub filters: Vec, + } + + impl T { + /// Creates a new filter pipeline. + #[inline] + pub fn new(filters: Vec) -> T { + T { + filters: filters, + } + } + + /// Adds a new filter to the filter pipeline. + #[inline] + pub fn push(&mut self, filter: Filter) { + self.filters.push(filter) + } + + /// Returns true if this filter pipeline is empty and false otherwise. + #[inline] + pub fn is_empty(&self) -> bool { + self.filters.is_empty() + } + + /// Returns the resulting opacity of this filter pipeline. + #[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 + } + } + opacity + } + } + } + + // TODO(pcwalton): `blur`, `drop-shadow` + #[deriving(Clone, Show)] + pub enum SpecifiedFilter { + Brightness(CSSFloat), + Contrast(CSSFloat), + Grayscale(CSSFloat), + HueRotate(Angle), + Invert(CSSFloat), + Opacity(CSSFloat), + Saturate(CSSFloat), + Sepia(CSSFloat), + } + + pub type SpecifiedValue = Vec; + + #[inline] + pub fn get_initial_value() -> computed_value::T { + computed_value::T::new(Vec::new()) + } + + pub fn to_computed_value(value: SpecifiedValue, _: &computed::Context) + -> computed_value::T { + computed_value::T::new(value.into_iter().map(|filter| { + match filter { + SpecifiedFilter::Brightness(amount) => { + computed_value::Filter::Brightness(amount) + } + SpecifiedFilter::Contrast(amount) => computed_value::Filter::Contrast(amount), + SpecifiedFilter::Grayscale(amount) => { + computed_value::Filter::Grayscale(amount) + } + SpecifiedFilter::HueRotate(angle) => computed_value::Filter::HueRotate(angle), + SpecifiedFilter::Invert(amount) => computed_value::Filter::Invert(amount), + SpecifiedFilter::Opacity(amount) => computed_value::Filter::Opacity(amount), + SpecifiedFilter::Saturate(amount) => computed_value::Filter::Saturate(amount), + SpecifiedFilter::Sepia(amount) => computed_value::Filter::Sepia(amount), + } + }).collect()) + } + + pub fn parse(input: &[ComponentValue], _: &Url) -> Result { + let mut filters = Vec::new(); + for filter in input.skip_whitespace() { + let name; + let args; + match *filter { + Function(ref function_name, ref function_args) => { + name = function_name; + args = function_args; + } + _ => return Err(()), + } + + if name.eq_ignore_ascii_case("brightness") && args.len() == 1 { + filters.push(SpecifiedFilter::Brightness(try!(parse_percentage(&args[0])))); + } else if name.eq_ignore_ascii_case("contrast") && args.len() == 1 { + filters.push(SpecifiedFilter::Contrast(try!(parse_percentage(&args[0])))); + } else if name.eq_ignore_ascii_case("grayscale") && args.len() == 1 { + filters.push(SpecifiedFilter::Grayscale(try!(parse_percentage(&args[0])))); + } else if name.eq_ignore_ascii_case("hue-rotate") && args.len() == 1 { + filters.push(SpecifiedFilter::HueRotate(try!(Angle::parse(&args[0])))); + } else if name.eq_ignore_ascii_case("invert") && args.len() == 1 { + filters.push(SpecifiedFilter::Invert(try!(parse_percentage(&args[0])))); + } else if name.eq_ignore_ascii_case("opacity") && args.len() == 1 { + filters.push(SpecifiedFilter::Opacity(try!(parse_percentage(&args[0])))); + } else if name.eq_ignore_ascii_case("saturate") && args.len() == 1 { + filters.push(SpecifiedFilter::Saturate(try!(parse_percentage(&args[0])))); + } else if name.eq_ignore_ascii_case("sepia") && args.len() == 1 { + filters.push(SpecifiedFilter::Sepia(try!(parse_percentage(&args[0])))); + } else { + return Err(()) + } + } + Ok(filters) + } + + fn parse_percentage(input: &ComponentValue) -> Result { + match *input { + Number(ref value) => Ok(value.value), + Percentage(ref value) => Ok(value.value / 100.0), + _ => Err(()) + } + } + } diff --git a/ports/cef/Cargo.lock b/ports/cef/Cargo.lock index af738e810a0..79a672ab4c7 100644 --- a/ports/cef/Cargo.lock +++ b/ports/cef/Cargo.lock @@ -27,7 +27,7 @@ dependencies = [ [[package]] name = "azure" version = "0.1.0" -source = "git+https://github.com/servo/rust-azure#6884d442052becfcafccc82b17ab316c7831d8d2" +source = "git+https://github.com/servo/rust-azure#e1a91efa633f155da89c6a1dadb0049d847b319d" dependencies = [ "core_foundation 0.1.0 (git+https://github.com/servo/rust-core-foundation)", "core_graphics 0.1.0 (git+https://github.com/servo/rust-core-graphics)", diff --git a/ports/gonk/Cargo.lock b/ports/gonk/Cargo.lock index db5159c4bc6..f6021634e70 100644 --- a/ports/gonk/Cargo.lock +++ b/ports/gonk/Cargo.lock @@ -15,7 +15,7 @@ dependencies = [ [[package]] name = "azure" version = "0.1.0" -source = "git+https://github.com/servo/rust-azure#6884d442052becfcafccc82b17ab316c7831d8d2" +source = "git+https://github.com/servo/rust-azure#e1a91efa633f155da89c6a1dadb0049d847b319d" dependencies = [ "core_foundation 0.1.0 (git+https://github.com/servo/rust-core-foundation)", "core_graphics 0.1.0 (git+https://github.com/servo/rust-core-graphics)", diff --git a/tests/ref/basic.list b/tests/ref/basic.list index 6c3faf2688c..94f35425363 100644 --- a/tests/ref/basic.list +++ b/tests/ref/basic.list @@ -226,3 +226,5 @@ fragment=top != ../html/acid2.html acid2_ref.html == stacking_context_overflow_relative_outline_a.html stacking_context_overflow_relative_outline_ref.html == word_break_a.html word_break_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 diff --git a/tests/ref/filter_opacity_a.html b/tests/ref/filter_opacity_a.html new file mode 100644 index 00000000000..efd8de5bc82 --- /dev/null +++ b/tests/ref/filter_opacity_a.html @@ -0,0 +1,15 @@ + + + + + + + + + + diff --git a/tests/ref/filter_opacity_ref.html b/tests/ref/filter_opacity_ref.html new file mode 100644 index 00000000000..98e0d68ba08 --- /dev/null +++ b/tests/ref/filter_opacity_ref.html @@ -0,0 +1,16 @@ + + + + + + + + + + + diff --git a/tests/ref/filter_sepia_a.html b/tests/ref/filter_sepia_a.html new file mode 100644 index 00000000000..ceb4bd307a8 --- /dev/null +++ b/tests/ref/filter_sepia_a.html @@ -0,0 +1,18 @@ + + + + + + +
+ + + diff --git a/tests/ref/filter_sepia_ref.html b/tests/ref/filter_sepia_ref.html new file mode 100644 index 00000000000..1fcf8ddb698 --- /dev/null +++ b/tests/ref/filter_sepia_ref.html @@ -0,0 +1,17 @@ + + + + + + +
+ + +