layout: Implement filter per CSS-FILTERS § 5.

`blur` and `drop-shadow` are not yet supported, because the
`text-shadow` PR makes some fundamental changes to blur rendering that
are needed first.
This commit is contained in:
Patrick Walton 2015-01-05 19:22:02 -08:00
parent 6b3c05cdd2
commit 15d60d7ea4
20 changed files with 499 additions and 62 deletions

View file

@ -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<Au>,
/// 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<Au>,
overflow: &Rect<Au>,
z_index: i32,
opacity: AzFloat,
filters: filter::T,
layer: Option<Arc<PaintLayer>>)
-> 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<AzFloat>,
clip_rect: Option<&Rect<Au>>) {
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.

198
components/gfx/filters.rs Normal file
View file

@ -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,
}
}

View file

@ -30,14 +30,14 @@ use azure::scaled_font::FontInfo;
#[cfg(any(target_os="linux", target_os = "android"))]
fn create_scaled_font(template: &Arc<FontTemplateData>, 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<FontTemplateData>, 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)

View file

@ -68,6 +68,7 @@ pub mod font_template;
// Misc.
mod buffer_map;
mod filters;
// Platform-specific implementations.
#[path="platform/mod.rs"]

View file

@ -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<Au>, 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)),

View file

@ -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);