servo: Merge #5133 - layout: Implement `image-rendering` per CSS-IMAGES-3 § 5.3 and (from servo:background-size); r=SimonSapin

`background-size` per CSS-BACKGROUNDS § 3.9.

Nearest neighbor interpolation is used for `crisp-edges`, like Firefox.
A note has been added that we could do better if we wanted to.

Multiple backgrounds are not yet supported.

Rebase of #4368. Fixes #4368.

Source-Repo: https://github.com/servo/servo
Source-Revision: e1a50c771973fe3223cf98feea5d570375d68fa9
This commit is contained in:
Simon Sapin 2015-03-03 11:48:54 -07:00
Родитель 73082d1626
Коммит 4578a773a4
6 изменённых файлов: 315 добавлений и 34 удалений

Просмотреть файл

@ -42,8 +42,9 @@ use util::smallvec::{SmallVec, SmallVec8};
use std::fmt;
use std::slice::Iter;
use std::sync::Arc;
use style::computed_values::{border_style, cursor, filter, image_rendering, mix_blend_mode};
use style::computed_values::{pointer_events};
use style::properties::ComputedValues;
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.
@ -763,6 +764,10 @@ pub struct ImageDisplayItem {
/// the bounds of this display item, then the image will be repeated in the appropriate
/// direction to tile the entire bounds.
pub stretch_size: Size2D<Au>,
/// The algorithm we should use to stretch the image. See `image_rendering` in CSS-IMAGES-3 §
/// 5.3.
pub image_rendering: image_rendering::T,
}
/// Paints a gradient.
@ -937,7 +942,9 @@ 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(),
image_item.image_rendering.clone());
x_offset = x_offset + image_item.stretch_size.width;
}

Просмотреть файл

@ -37,7 +37,7 @@ use std::mem;
use std::num::Float;
use std::ptr;
use std::sync::Arc;
use style::computed_values::{border_style, filter, mix_blend_mode};
use style::computed_values::{border_style, filter, image_rendering, mix_blend_mode};
use util::geometry::{self, Au, MAX_RECT, ZERO_RECT};
use util::opts;
use util::range::Range;
@ -127,7 +127,10 @@ 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>>,
image_rendering: image_rendering::T) {
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),
@ -146,7 +149,17 @@ impl<'a> PaintContext<'a> {
let source_rect = Rect(Point2D(0.0, 0.0),
Size2D(image.width as AzFloat, image.height as AzFloat));
let dest_rect = bounds.to_azure_rect();
let draw_surface_options = DrawSurfaceOptions::new(Filter::Linear, true);
// TODO(pcwalton): According to CSS-IMAGES-3 § 5.3, nearest-neighbor interpolation is a
// conforming implementation of `crisp-edges`, but it is not the best we could do.
// Something like Scale2x would be ideal.
let draw_surface_options = match image_rendering {
image_rendering::T::Auto => DrawSurfaceOptions::new(Filter::Linear, true),
image_rendering::T::CrispEdges | image_rendering::T::Pixelated => {
DrawSurfaceOptions::new(Filter::Point, true)
}
};
let draw_options = DrawOptions::new(1.0, 0);
draw_target_ref.draw_surface(azure_surface,
dest_rect,

Просмотреть файл

@ -18,7 +18,7 @@ use fragment::{CoordinateSystem, Fragment, IframeFragmentInfo, ImageFragmentInfo
use fragment::{ScannedTextFragmentInfo, SpecificFragmentInfo};
use inline::InlineFlow;
use list_item::ListItemFlow;
use model;
use model::{self, MaybeAuto};
use util::{OpaqueNodeMethods, ToGfxColor};
use geom::{Point2D, Rect, Size2D, SideOffsets2D};
@ -31,8 +31,7 @@ use gfx::display_list::{GradientStop, ImageDisplayItem, LineDisplayItem};
use gfx::display_list::{OpaqueNode, SolidColorDisplayItem};
use gfx::display_list::{StackingContext, TextDisplayItem, TextOrientation};
use gfx::paint_task::{PaintLayer, THREAD_TINT_COLORS};
use png;
use png::PixelsByColorType;
use png::{self, PixelsByColorType};
use msg::compositor_msg::ScrollPolicy;
use msg::constellation_msg::Msg as ConstellationMsg;
use msg::constellation_msg::ConstellationChan;
@ -46,11 +45,11 @@ use std::default::Default;
use std::iter::repeat;
use std::num::Float;
use style::values::specified::{AngleOrCorner, HorizontalDirection, VerticalDirection};
use style::values::computed::{Image, LinearGradient, LengthOrPercentage};
use style::values::computed::{Image, LinearGradient, LengthOrPercentage, LengthOrPercentageOrAuto};
use style::values::RGBA;
use style::computed_values::filter::Filter;
use style::computed_values::{background_attachment, background_repeat, border_style, overflow_x};
use style::computed_values::{position, visibility};
use style::computed_values::{background_attachment, background_repeat, background_size};
use style::computed_values::{border_style, image_rendering, overflow_x, position, visibility};
use style::properties::style_structs::Border;
use style::properties::ComputedValues;
use std::num::ToPrimitive;
@ -93,6 +92,14 @@ pub trait FragmentDisplayListBuilding {
absolute_bounds: &Rect<Au>,
clip: &ClippingRegion);
/// Computes the background size for an image with the given background area according to the
/// rules in CSS-BACKGROUNDS § 3.9.
fn compute_background_image_size(&self,
style: &ComputedValues,
bounds: &Rect<Au>,
image: &png::Image)
-> Size2D<Au>;
/// Adds the display items necessary to paint the background image of this fragment to the
/// display list at the appropriate stacking level.
fn build_display_list_for_background_image(&self,
@ -326,6 +333,59 @@ impl FragmentDisplayListBuilding for Fragment {
}
}
fn compute_background_image_size(&self,
style: &ComputedValues,
bounds: &Rect<Au>,
image: &png::Image)
-> Size2D<Au> {
// If `image_aspect_ratio` < `bounds_aspect_ratio`, the image is tall; otherwise, it is
// wide.
let image_aspect_ratio = (image.width as f64) / (image.height as f64);
let bounds_aspect_ratio = bounds.size.width.to_subpx() / bounds.size.height.to_subpx();
let intrinsic_size = Size2D(Au::from_px(image.width as int),
Au::from_px(image.height as int));
match (style.get_background().background_size.clone(),
image_aspect_ratio < bounds_aspect_ratio) {
(background_size::T::Contain, false) | (background_size::T::Cover, true) => {
Size2D(bounds.size.width,
Au::from_frac_px(bounds.size.width.to_subpx() / image_aspect_ratio))
}
(background_size::T::Contain, true) | (background_size::T::Cover, false) => {
Size2D(Au::from_frac_px(bounds.size.height.to_subpx() * image_aspect_ratio),
bounds.size.height)
}
(background_size::T::Explicit(background_size::ExplicitSize {
width,
height: LengthOrPercentageOrAuto::Auto,
}), _) => {
let width = MaybeAuto::from_style(width, bounds.size.width)
.specified_or_default(intrinsic_size.width);
Size2D(width, Au::from_frac_px(width.to_subpx() / image_aspect_ratio))
}
(background_size::T::Explicit(background_size::ExplicitSize {
width: LengthOrPercentageOrAuto::Auto,
height
}), _) => {
let height = MaybeAuto::from_style(height, bounds.size.height)
.specified_or_default(intrinsic_size.height);
Size2D(Au::from_frac_px(height.to_subpx() * image_aspect_ratio), height)
}
(background_size::T::Explicit(background_size::ExplicitSize {
width,
height
}), _) => {
Size2D(MaybeAuto::from_style(width, bounds.size.width)
.specified_or_default(intrinsic_size.width),
MaybeAuto::from_style(height, bounds.size.height)
.specified_or_default(intrinsic_size.height))
}
}
}
fn build_display_list_for_background_image(&self,
style: &ComputedValues,
display_list: &mut DisplayList,
@ -349,16 +409,16 @@ impl FragmentDisplayListBuilding for Fragment {
};
debug!("(building display list) building background image");
let image_width = Au::from_px(image.width as int);
let image_height = Au::from_px(image.height as int);
// Use `background-size` to get the size.
let mut bounds = *absolute_bounds;
let image_size = self.compute_background_image_size(style, &bounds, &**image);
// Clip.
//
// TODO: Check the bounds to see if a clip item is actually required.
let clip = clip.clone().intersect_rect(&bounds);
// Use background-attachment to get the initial virtual origin
// Use `background-attachment` to get the initial virtual origin
let (virtual_origin_x, virtual_origin_y) = match background.background_attachment {
background_attachment::T::scroll => {
(absolute_bounds.origin.x, absolute_bounds.origin.y)
@ -368,11 +428,11 @@ impl FragmentDisplayListBuilding for Fragment {
}
};
// Use background-position to get the offset
// Use `background-position` to get the offset.
let horizontal_position = model::specified(background.background_position.horizontal,
bounds.size.width - image_width);
bounds.size.width - image_size.width);
let vertical_position = model::specified(background.background_position.vertical,
bounds.size.height - image_height);
bounds.size.height - image_size.height);
let abs_x = virtual_origin_x + horizontal_position;
let abs_y = virtual_origin_y + vertical_position;
@ -382,26 +442,34 @@ impl FragmentDisplayListBuilding for Fragment {
background_repeat::T::no_repeat => {
bounds.origin.x = abs_x;
bounds.origin.y = abs_y;
bounds.size.width = image_width;
bounds.size.height = image_height;
bounds.size.width = image_size.width;
bounds.size.height = image_size.height;
}
background_repeat::T::repeat_x => {
bounds.origin.y = abs_y;
bounds.size.height = image_height;
ImageFragmentInfo::tile_image(&mut bounds.origin.x, &mut bounds.size.width,
abs_x, image.width);
bounds.size.height = image_size.height;
ImageFragmentInfo::tile_image(&mut bounds.origin.x,
&mut bounds.size.width,
abs_x,
image_size.width.to_nearest_px() as u32);
}
background_repeat::T::repeat_y => {
bounds.origin.x = abs_x;
bounds.size.width = image_width;
ImageFragmentInfo::tile_image(&mut bounds.origin.y, &mut bounds.size.height,
abs_y, image.height);
bounds.size.width = image_size.width;
ImageFragmentInfo::tile_image(&mut bounds.origin.y,
&mut bounds.size.height,
abs_y,
image_size.height.to_nearest_px() as u32);
}
background_repeat::T::repeat => {
ImageFragmentInfo::tile_image(&mut bounds.origin.x, &mut bounds.size.width,
abs_x, image.width);
ImageFragmentInfo::tile_image(&mut bounds.origin.y, &mut bounds.size.height,
abs_y, image.height);
ImageFragmentInfo::tile_image(&mut bounds.origin.x,
&mut bounds.size.width,
abs_x,
image_size.width.to_nearest_px() as u32);
ImageFragmentInfo::tile_image(&mut bounds.origin.y,
&mut bounds.size.height,
abs_y,
image_size.height.to_nearest_px() as u32);
}
};
@ -413,8 +481,8 @@ impl FragmentDisplayListBuilding for Fragment {
Cursor::DefaultCursor),
clip),
image: image.clone(),
stretch_size: Size2D(Au::from_px(image.width as int),
Au::from_px(image.height as int)),
stretch_size: Size2D(image_size.width, image_size.height),
image_rendering: style.get_effects().image_rendering.clone(),
}), level);
}
@ -913,6 +981,7 @@ impl FragmentDisplayListBuilding for Fragment {
(*clip).clone()),
image: image.clone(),
stretch_size: stacking_relative_content_box.size,
image_rendering: self.style.get_effects().image_rendering.clone(),
}));
} else {
// No image data at all? Do nothing.
@ -948,6 +1017,7 @@ impl FragmentDisplayListBuilding for Fragment {
pixels: PixelsByColorType::RGBA8(canvas_data),
}),
stretch_size: stacking_relative_content_box.size,
image_rendering: image_rendering::T::Auto,
};
display_list.content.push_back(DisplayItem::ImageClass(canvas_display_item));

Просмотреть файл

@ -38,6 +38,7 @@ partial interface CSSStyleDeclaration {
[TreatNullAs=EmptyString] attribute DOMString backgroundRepeat;
[TreatNullAs=EmptyString] attribute DOMString backgroundImage;
[TreatNullAs=EmptyString] attribute DOMString backgroundAttachment;
[TreatNullAs=EmptyString] attribute DOMString backgroundSize;
[TreatNullAs=EmptyString] attribute DOMString border;
[TreatNullAs=EmptyString] attribute DOMString borderColor;
@ -178,4 +179,6 @@ partial interface CSSStyleDeclaration {
[TreatNullAs=EmptyString] attribute DOMString maxWidth;
[TreatNullAs=EmptyString] attribute DOMString zIndex;
[TreatNullAs=EmptyString] attribute DOMString imageRendering;
};

Просмотреть файл

@ -1286,6 +1286,126 @@ pub mod longhands {
${single_keyword("background-attachment", "scroll fixed")}
<%self:longhand name="background-size">
use cssparser::{ToCss, Token};
use std::ascii::AsciiExt;
use text_writer::{self, TextWriter};
use values::computed::{Context, ToComputedValue};
pub mod computed_value {
use values::computed::LengthOrPercentageOrAuto;
#[derive(PartialEq, Clone, Debug)]
pub struct ExplicitSize {
pub width: LengthOrPercentageOrAuto,
pub height: LengthOrPercentageOrAuto,
}
#[derive(PartialEq, Clone, Debug)]
pub enum T {
Explicit(ExplicitSize),
Cover,
Contain,
}
}
#[derive(Clone, PartialEq, Debug)]
pub struct SpecifiedExplicitSize {
pub width: specified::LengthOrPercentageOrAuto,
pub height: specified::LengthOrPercentageOrAuto,
}
impl ToCss for SpecifiedExplicitSize {
fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
try!(self.width.to_css(dest));
try!(dest.write_str(" "));
self.height.to_css(dest)
}
}
#[derive(Clone, PartialEq, Debug)]
pub enum SpecifiedValue {
Explicit(SpecifiedExplicitSize),
Cover,
Contain,
}
impl ToCss for SpecifiedValue {
fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
match *self {
SpecifiedValue::Explicit(ref size) => size.to_css(dest),
SpecifiedValue::Cover => dest.write_str("cover"),
SpecifiedValue::Contain => dest.write_str("contain"),
}
}
}
impl ToComputedValue for SpecifiedValue {
type ComputedValue = computed_value::T;
#[inline]
fn to_computed_value(&self, context: &computed::Context) -> computed_value::T {
match *self {
SpecifiedValue::Explicit(ref size) => {
computed_value::T::Explicit(computed_value::ExplicitSize {
width: size.width.to_computed_value(context),
height: size.height.to_computed_value(context),
})
}
SpecifiedValue::Cover => computed_value::T::Cover,
SpecifiedValue::Contain => computed_value::T::Contain,
}
}
}
#[inline]
pub fn get_initial_value() -> computed_value::T {
computed_value::T::Explicit(computed_value::ExplicitSize {
width: computed::LengthOrPercentageOrAuto::Auto,
height: computed::LengthOrPercentageOrAuto::Auto,
})
}
pub fn parse(_: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
let width;
if let Ok(value) = input.try(|input| {
match input.next() {
Err(_) => Err(()),
Ok(Token::Ident(ref ident)) if ident.as_slice()
.eq_ignore_ascii_case("cover") => {
Ok(SpecifiedValue::Cover)
}
Ok(Token::Ident(ref ident)) if ident.as_slice()
.eq_ignore_ascii_case("contain") => {
Ok(SpecifiedValue::Contain)
}
Ok(_) => Err(()),
}
}) {
return Ok(value)
} else {
width = try!(specified::LengthOrPercentageOrAuto::parse(input))
}
let height;
if let Ok(value) = input.try(|input| {
match input.next() {
Err(_) => Ok(specified::LengthOrPercentageOrAuto::Auto),
Ok(_) => Err(()),
}
}) {
height = value
} else {
height = try!(specified::LengthOrPercentageOrAuto::parse(input));
}
Ok(SpecifiedValue::Explicit(SpecifiedExplicitSize {
width: width,
height: height,
}))
}
</%self:longhand>
${new_style_struct("Color", is_inherited=True)}
<%self:raw_longhand name="color">
@ -2628,6 +2748,62 @@ pub mod longhands {
"""normal multiply screen overlay darken lighten color-dodge
color-burn hard-light soft-light difference exclusion hue
saturation color luminosity""")}
<%self:longhand name="image-rendering">
use values::computed::{Context, ToComputedValue};
pub mod computed_value {
use cssparser::ToCss;
use text_writer::{self, TextWriter};
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum T {
Auto,
CrispEdges,
Pixelated,
}
impl ToCss for T {
fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
match *self {
T::Auto => dest.write_str("auto"),
T::CrispEdges => dest.write_str("crisp-edges"),
T::Pixelated => dest.write_str("pixelated"),
}
}
}
}
pub type SpecifiedValue = computed_value::T;
#[inline]
pub fn get_initial_value() -> computed_value::T {
computed_value::T::Auto
}
pub fn parse(_: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
// According to to CSS-IMAGES-3, `optimizespeed` and `optimizequality` are synonyms for
// `auto`.
match_ignore_ascii_case! {
try!(input.expect_ident()),
"auto" => Ok(computed_value::T::Auto),
"optimizespeed" => Ok(computed_value::T::Auto),
"optimizequality" => Ok(computed_value::T::Auto),
"crisp-edges" => Ok(computed_value::T::CrispEdges),
"pixelated" => Ok(computed_value::T::Pixelated)
_ => Err(())
}
}
impl ToComputedValue for SpecifiedValue {
type ComputedValue = computed_value::T;
#[inline]
fn to_computed_value(&self, _: &Context) -> computed_value::T {
*self
}
}
</%self:longhand>
}
@ -2720,14 +2896,17 @@ pub mod shorthands {
// TODO: other background-* properties
<%self:shorthand name="background"
sub_properties="background-color background-position background-repeat background-attachment background-image">
use properties::longhands::{background_color, background_position, background_repeat,
background_attachment, background_image};
sub_properties="background-color background-position background-repeat background-attachment background-image background-size">
use properties::longhands::{background_color, background_position, background_repeat};
use properties::longhands::{background_attachment, background_image, background_size};
use cssparser::Token;
let mut color = None;
let mut image = None;
let mut position = None;
let mut repeat = None;
let mut size = None;
let mut attachment = None;
let mut any = false;
@ -2736,6 +2915,13 @@ pub mod shorthands {
if let Ok(value) = input.try(|input| background_position::parse(context, input)) {
position = Some(value);
any = true;
// Parse background size, if applicable.
size = input.try(|input| {
try!(input.expect_delim('/'));
background_size::parse(context, input)
}).ok();
continue
}
}
@ -2777,6 +2963,7 @@ pub mod shorthands {
background_position: position,
background_repeat: repeat,
background_attachment: attachment,
background_size: size,
})
} else {
Err(())

Просмотреть файл

@ -435,6 +435,7 @@ fn test_parse_stylesheet() {
],
declarations: PropertyDeclarationBlock {
normal: Arc::new(vec![
PropertyDeclaration::BackgroundSize(DeclaredValue::Initial),
PropertyDeclaration::BackgroundImage(DeclaredValue::Initial),
PropertyDeclaration::BackgroundAttachment(DeclaredValue::Initial),
PropertyDeclaration::BackgroundRepeat(DeclaredValue::Initial),