aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPatrick Walton <pcwalton@mimiga.net>2014-12-13 19:09:06 -0800
committerSimon Sapin <simon.sapin@exyr.org>2015-03-03 17:54:12 +0100
commit09c53f461dadf0c1f2896754c329ee6ad3e9359e (patch)
treee7ff2e7cd5e29034ad3914ddeb231447ff883e1a
parent6fcc02e92f4c519239a834dc37a2965a4993322a (diff)
downloadservo-09c53f461dadf0c1f2896754c329ee6ad3e9359e.tar.gz
servo-09c53f461dadf0c1f2896754c329ee6ad3e9359e.zip
layout: Implement `image-rendering` per CSS-IMAGES-3 § 5.3 and
`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.
-rw-r--r--components/gfx/display_list/mod.rs11
-rw-r--r--components/gfx/paint_context.rs19
-rw-r--r--components/layout/display_list_builder.rs122
-rw-r--r--components/script/dom/webidls/CSSStyleDeclaration.webidl3
-rw-r--r--components/style/properties.mako.rs193
-rw-r--r--tests/ref/2x4.pngbin0 -> 171 bytes
-rw-r--r--tests/ref/4x2.pngbin0 -> 180 bytes
-rw-r--r--tests/ref/background_size.pngbin0 -> 510 bytes
-rw-r--r--tests/ref/background_size_a.html62
-rw-r--r--tests/ref/background_size_ref.html19
-rw-r--r--tests/ref/background_size_shorthand_a.html28
-rw-r--r--tests/ref/background_size_shorthand_ref.html23
-rw-r--r--tests/ref/basic.list4
-rw-r--r--tests/ref/image_rendering_auto_a.html11
-rw-r--r--tests/ref/image_rendering_pixelated_a.html20
-rw-r--r--tests/ref/image_rendering_pixelated_ref.html40
16 files changed, 521 insertions, 34 deletions
diff --git a/components/gfx/display_list/mod.rs b/components/gfx/display_list/mod.rs
index 9ba4d93e9e9..fde17c0aed5 100644
--- a/components/gfx/display_list/mod.rs
+++ b/components/gfx/display_list/mod.rs
@@ -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;
}
diff --git a/components/gfx/paint_context.rs b/components/gfx/paint_context.rs
index 82184bf06de..442e13f7333 100644
--- a/components/gfx/paint_context.rs
+++ b/components/gfx/paint_context.rs
@@ -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,
diff --git a/components/layout/display_list_builder.rs b/components/layout/display_list_builder.rs
index e20ade0ce19..e61e8728eb6 100644
--- a/components/layout/display_list_builder.rs
+++ b/components/layout/display_list_builder.rs
@@ -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);
}
@@ -912,6 +980,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.
@@ -947,6 +1016,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));
diff --git a/components/script/dom/webidls/CSSStyleDeclaration.webidl b/components/script/dom/webidls/CSSStyleDeclaration.webidl
index d6a4420b2a9..46ca8424b70 100644
--- a/components/script/dom/webidls/CSSStyleDeclaration.webidl
+++ b/components/script/dom/webidls/CSSStyleDeclaration.webidl
@@ -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;
@@ -173,4 +174,6 @@ partial interface CSSStyleDeclaration {
[TreatNullAs=EmptyString] attribute DOMString maxWidth;
[TreatNullAs=EmptyString] attribute DOMString zIndex;
+
+ [TreatNullAs=EmptyString] attribute DOMString imageRendering;
};
diff --git a/components/style/properties.mako.rs b/components/style/properties.mako.rs
index 83d6570a800..5330de59a93 100644
--- a/components/style/properties.mako.rs
+++ b/components/style/properties.mako.rs
@@ -1073,6 +1073,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">
@@ -2415,6 +2535,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>
}
@@ -2507,14 +2683,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;
@@ -2523,6 +2702,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
}
}
@@ -2564,6 +2750,7 @@ pub mod shorthands {
background_position: position,
background_repeat: repeat,
background_attachment: attachment,
+ background_size: size,
})
} else {
Err(())
diff --git a/tests/ref/2x4.png b/tests/ref/2x4.png
new file mode 100644
index 00000000000..7efc762459b
--- /dev/null
+++ b/tests/ref/2x4.png
Binary files differ
diff --git a/tests/ref/4x2.png b/tests/ref/4x2.png
new file mode 100644
index 00000000000..eb0c5cda971
--- /dev/null
+++ b/tests/ref/4x2.png
Binary files differ
diff --git a/tests/ref/background_size.png b/tests/ref/background_size.png
new file mode 100644
index 00000000000..8d5824940c0
--- /dev/null
+++ b/tests/ref/background_size.png
Binary files differ
diff --git a/tests/ref/background_size_a.html b/tests/ref/background_size_a.html
new file mode 100644
index 00000000000..aa2df4fb8b2
--- /dev/null
+++ b/tests/ref/background_size_a.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+section {
+ position: absolute;
+ top: 0;
+ width: 40px;
+}
+#tall {
+ left: 0;
+}
+#wide {
+ left: 40px;
+}
+div {
+ width: 40px;
+ height: 40px;
+ image-rendering: -moz-crisp-edges; /* for comparison with Firefox */
+ image-rendering: pixelated;
+}
+#tall div {
+ background-image: url(2x4.png);
+}
+#wide div {
+ background-image: url(4x2.png);
+}
+.a {
+ background-size: 40px 20px;
+}
+.b {
+ background-size: 40px 40px;
+}
+.c {
+ background-size: 40px;
+}
+.d {
+ background-size: cover;
+}
+.e {
+ background-size: contain;
+}
+</style>
+</head>
+<body>
+<section id=tall>
+<div class=a></div>
+<div class=b></div>
+<div class=c></div>
+<div class=d></div>
+<div class=e></div>
+</section>
+<section id=wide>
+<div class=a></div>
+<div class=b></div>
+<div class=c></div>
+<div class=d></div>
+<div class=e></div>
+</section>
+</body>
+</html>
+
diff --git a/tests/ref/background_size_ref.html b/tests/ref/background_size_ref.html
new file mode 100644
index 00000000000..7967b10ae09
--- /dev/null
+++ b/tests/ref/background_size_ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+img {
+ image-rendering: -moz-crisp-edges; /* for comparison with Firefox */
+ image-rendering: pixelated;
+}
+html, body {
+ margin: 0;
+}
+</style>
+</head>
+<body>
+<img src=background_size.png>
+</body>
+</html>
+
+
diff --git a/tests/ref/background_size_shorthand_a.html b/tests/ref/background_size_shorthand_a.html
new file mode 100644
index 00000000000..30ce3a9cef2
--- /dev/null
+++ b/tests/ref/background_size_shorthand_a.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+<!-- Tests that the `background-size` shorthand works. -->
+<style>
+section {
+ width: 100px;
+ height: 100px;
+ border: solid black 1px;
+}
+#a {
+ background: 10px 10px / 80px 80px url(rust_logo.png) no-repeat;
+}
+#b {
+ background: url(rust_logo.png) 10px 10px / 80px 80px no-repeat;
+}
+#c {
+ background: no-repeat url(rust_logo.png) 10px 10px / 80px 80px;
+}
+</style>
+</head>
+<body>
+<section id=a>&nbsp;</section>
+<section id=b>&nbsp;</section>
+<section id=c>&nbsp;</section>
+</body>
+</html>
+
diff --git a/tests/ref/background_size_shorthand_ref.html b/tests/ref/background_size_shorthand_ref.html
new file mode 100644
index 00000000000..48766310e1c
--- /dev/null
+++ b/tests/ref/background_size_shorthand_ref.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+<!-- Tests that the `background-size` shorthand works. -->
+<style>
+section {
+ width: 100px;
+ height: 100px;
+ border: solid black 1px;
+ background-image: url(rust_logo.png);
+ background-position: 10px 10px;
+ background-size: 80px 80px;
+ background-repeat: no-repeat;
+}
+</style>
+</head>
+<body>
+<section>&nbsp;</section>
+<section>&nbsp;</section>
+<section>&nbsp;</section>
+</body>
+</html>
+
diff --git a/tests/ref/basic.list b/tests/ref/basic.list
index 2361eb2e7df..2aec5daa48d 100644
--- a/tests/ref/basic.list
+++ b/tests/ref/basic.list
@@ -259,3 +259,7 @@ fragment=top != ../html/acid2.html acid2_ref.html
== text_shadow_simple_a.html text_shadow_simple_ref.html
== text_shadow_decorations_a.html text_shadow_decorations_ref.html
== text_shadow_blur_a.html text_shadow_blur_ref.html
+!= image_rendering_auto_a.html image_rendering_pixelated_a.html
+== image_rendering_pixelated_a.html image_rendering_pixelated_ref.html
+== background_size_a.html background_size_ref.html
+== background_size_shorthand_a.html background_size_shorthand_ref.html
diff --git a/tests/ref/image_rendering_auto_a.html b/tests/ref/image_rendering_auto_a.html
new file mode 100644
index 00000000000..3b7282e5ff9
--- /dev/null
+++ b/tests/ref/image_rendering_auto_a.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+<!-- Tests that `image-rendering: auto` uses bilinear filtering. -->
+</head>
+<body>
+<img width=100 height=50 src=4x2.png>
+</body>
+</html>
+
+
diff --git a/tests/ref/image_rendering_pixelated_a.html b/tests/ref/image_rendering_pixelated_a.html
new file mode 100644
index 00000000000..b4086e2fd33
--- /dev/null
+++ b/tests/ref/image_rendering_pixelated_a.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<!-- Tests that `image-rendering: pixelated` causes nearest-neighbor interpolation to be used. -->
+<style>
+img {
+ position: absolute;
+ top: 0;
+ left: 0;
+ image-rendering: -moz-crisp-edges; /* for testing in Firefox */
+ image-rendering: pixelated;
+}
+</style>
+</head>
+<body>
+<img width=100 height=50 src=4x2.png>
+</body>
+</html>
+
+
diff --git a/tests/ref/image_rendering_pixelated_ref.html b/tests/ref/image_rendering_pixelated_ref.html
new file mode 100644
index 00000000000..06b2f3249f6
--- /dev/null
+++ b/tests/ref/image_rendering_pixelated_ref.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+<head>
+<!-- Tests that `image-rendering: pixelated` causes nearest-neighbor interpolation to be used. -->
+<style>
+section {
+ position: absolute;
+ width: 50px;
+ height: 25px;
+}
+#a, #d {
+ background: red;
+}
+#b, #c {
+ background: blue;
+}
+#a, #b {
+ top: 0;
+}
+#c, #d {
+ top: 25px;
+}
+#a, #c {
+ left: 0;
+}
+#b, #d {
+ left: 50px;
+}
+</style>
+</head>
+<body>
+<section id=a></section>
+<section id=b></section>
+<section id=c></section>
+<section id=d></section>
+</body>
+</html>
+
+
+