diff options
author | Matt Brubeck <mbrubeck@limpet.net> | 2015-09-18 01:53:53 -0700 |
---|---|---|
committer | Matt Brubeck <mbrubeck@limpet.net> | 2015-09-18 03:06:06 -0700 |
commit | 9333f028e855416b1c35647c29a741bb51b7393f (patch) | |
tree | 969afd1b6c851c51de725547e4d9c54ec43edf9a | |
parent | 186ab5aa245862c686ba6349ba2af9e4662ad02f (diff) | |
download | servo-9333f028e855416b1c35647c29a741bb51b7393f.tar.gz servo-9333f028e855416b1c35647c29a741bb51b7393f.zip |
Snap to screen pixels instead of px
Fixes #7665.
-rw-r--r-- | components/gfx/paint_context.rs | 167 | ||||
-rw-r--r-- | components/util/geometry.rs | 5 | ||||
-rw-r--r-- | tests/ref/basic.list | 1 | ||||
-rw-r--r-- | tests/ref/pixel_snapping_border_a.html | 18 | ||||
-rw-r--r-- | tests/ref/pixel_snapping_border_ref.html | 18 |
5 files changed, 131 insertions, 78 deletions
diff --git a/components/gfx/paint_context.rs b/components/gfx/paint_context.rs index 7b52473ea96..62ac45bab79 100644 --- a/components/gfx/paint_context.rs +++ b/components/gfx/paint_context.rs @@ -93,13 +93,17 @@ struct CornerOrigin { } impl<'a> PaintContext<'a> { + pub fn screen_pixels_per_px(&self) -> f32 { + self.screen_rect.size.width as f32 / self.page_rect.size.width + } + pub fn draw_target(&self) -> &DrawTarget { &self.draw_target } pub fn draw_solid_color(&self, bounds: &Rect<Au>, color: Color) { self.draw_target.make_current(); - self.draw_target.fill_rect(&bounds.to_nearest_azure_rect(), + self.draw_target.fill_rect(&bounds.to_nearest_azure_rect(self.screen_pixels_per_px()), PatternRef::Color(&ColorPattern::new(color)), None); } @@ -110,8 +114,9 @@ impl<'a> PaintContext<'a> { radius: &BorderRadii<Au>, color: &SideOffsets2D<Color>, style: &SideOffsets2D<border_style::T>) { - let border = border.to_float_px(); - let radius = radius.to_radii_px(); + let scale = self.screen_pixels_per_px(); + let border = border.to_float_pixels(scale); + let radius = radius.to_radii_pixels(scale); self.draw_border_segment(Direction::Top, bounds, &border, &radius, color, style); self.draw_border_segment(Direction::Right, bounds, &border, &radius, color, style); @@ -126,7 +131,7 @@ impl<'a> PaintContext<'a> { } pub fn draw_push_clip(&self, bounds: &Rect<Au>) { - let rect = bounds.to_nearest_azure_rect(); + let rect = bounds.to_nearest_azure_rect(self.screen_pixels_per_px()); let path_builder = self.draw_target.create_path_builder(); let left_top = Point2D::new(rect.origin.x, rect.origin.y); @@ -161,6 +166,7 @@ impl<'a> PaintContext<'a> { PixelFormat::KA8 => panic!("KA8 color type not supported"), }; let stride = image.width * pixel_width; + let scale = self.screen_pixels_per_px(); self.draw_target.make_current(); let draw_target_ref = &self.draw_target; @@ -170,7 +176,7 @@ impl<'a> PaintContext<'a> { source_format); let source_rect = Rect::new(Point2D::new(0.0, 0.0), Size2D::new(image.width as AzFloat, image.height as AzFloat)); - let dest_rect = bounds.to_nearest_azure_rect(); + let dest_rect = bounds.to_nearest_azure_rect(scale); // 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. @@ -198,7 +204,7 @@ impl<'a> PaintContext<'a> { // Annoyingly, surface patterns in Azure/Skia are relative to the top left of the *canvas*, // not the rectangle we're drawing to. So we need to translate it explicitly. let matrix = Matrix2D::identity().translate(dest_rect.origin.x, dest_rect.origin.y); - let stretch_size = stretch_size.to_nearest_azure_size(); + let stretch_size = stretch_size.to_nearest_azure_size(scale); if source_rect.size == stretch_size { let pattern = SurfacePattern::new(azure_surface.azure_source_surface, true, @@ -306,7 +312,8 @@ impl<'a> PaintContext<'a> { radius: &BorderRadii<AzFloat>, color: Color, style: border_style::T) { - let border = SideOffsets2D::new_all_same(bounds.size.width).to_float_px(); + let scale = self.screen_pixels_per_px(); + let border = SideOffsets2D::new_all_same(bounds.size.width).to_float_pixels(scale); match style { border_style::T::none | border_style::T::hidden => {} border_style::T::dotted => { @@ -1027,7 +1034,7 @@ impl<'a> PaintContext<'a> { radius: &BorderRadii<AzFloat>, color: Color, dash_size: DashSize) { - let rect = bounds.to_nearest_azure_rect(); + let rect = bounds.to_nearest_azure_rect(self.screen_pixels_per_px()); let draw_opts = DrawOptions::new(1.0, CompositionOp::Over, AntialiasMode::None); let border_width = match direction { Direction::Top => border.top, @@ -1095,7 +1102,7 @@ impl<'a> PaintContext<'a> { border: &SideOffsets2D<f32>, radius: &BorderRadii<AzFloat>, color: Color) { - let rect = bounds.to_nearest_azure_rect(); + let rect = bounds.to_nearest_azure_rect(self.screen_pixels_per_px()); self.draw_border_path(&rect, direction, border, radius, color); } @@ -1103,7 +1110,7 @@ impl<'a> PaintContext<'a> { bounds: &Rect<Au>, border: &SideOffsets2D<f32>, shrink_factor: f32) -> Rect<f32> { - let rect = bounds.to_nearest_azure_rect(); + let rect = bounds.to_nearest_azure_rect(self.screen_pixels_per_px()); let scaled_border = SideOffsets2D::new(shrink_factor * border.top, shrink_factor * border.right, shrink_factor * border.bottom, @@ -1302,11 +1309,12 @@ impl<'a> PaintContext<'a> { self.draw_target.make_current(); let stops = self.draw_target.create_gradient_stops(stops, ExtendMode::Clamp); - let pattern = LinearGradientPattern::new(&start_point.to_nearest_azure_point(), - &end_point.to_nearest_azure_point(), + let scale = self.screen_pixels_per_px(); + let pattern = LinearGradientPattern::new(&start_point.to_nearest_azure_point(scale), + &end_point.to_nearest_azure_point(scale), stops, &Matrix2D::identity()); - self.draw_target.fill_rect(&bounds.to_nearest_azure_rect(), + self.draw_target.fill_rect(&bounds.to_nearest_azure_rect(scale), PatternRef::LinearGradient(&pattern), None); } @@ -1328,8 +1336,8 @@ impl<'a> PaintContext<'a> { } // FIXME(pcwalton): This surface might be bigger than necessary and waste memory. - let size = self.draw_target.get_size(); //Az size. - let mut size = Size2D::new(size.width, size.height); //Geom::Size. + let size: AzIntSize = self.draw_target.get_size(); + let mut size = Size2D::new(size.width, size.height); // Pre-calculate if there is a blur expansion need. let accum_blur = filters::calculate_accumulated_blur(filters); @@ -1424,6 +1432,7 @@ impl<'a> PaintContext<'a> { self.pop_clip_if_applicable(); // If we have blur, create a new draw target. + let pixels_per_px = self.screen_pixels_per_px(); let shadow_bounds = box_bounds.translate(offset).inflate(spread_radius, spread_radius); let side_inflation = blur_radius * BLUR_INFLATION_FACTOR; let inflated_shadow_bounds = shadow_bounds.inflate(side_inflation, side_inflation); @@ -1435,17 +1444,21 @@ impl<'a> PaintContext<'a> { BoxShadowClipMode::Inset => { path = temporary_draw_target.draw_target .create_rectangular_border_path(&MAX_RECT, - &shadow_bounds); - self.draw_target.push_clip(&self.draw_target.create_rectangular_path(box_bounds)) + &shadow_bounds, + pixels_per_px); + self.draw_target.push_clip( + &self.draw_target.create_rectangular_path(box_bounds, pixels_per_px)) } BoxShadowClipMode::Outset => { - path = temporary_draw_target.draw_target.create_rectangular_path(&shadow_bounds); - self.draw_target.push_clip(&self.draw_target - .create_rectangular_border_path(&MAX_RECT, - box_bounds)) + path = temporary_draw_target.draw_target.create_rectangular_path(&shadow_bounds, + pixels_per_px); + self.draw_target.push_clip( + &self.draw_target.create_rectangular_border_path(&MAX_RECT, box_bounds, + pixels_per_px)) } BoxShadowClipMode::None => { - path = temporary_draw_target.draw_target.create_rectangular_path(&shadow_bounds) + path = temporary_draw_target.draw_target.create_rectangular_path(&shadow_bounds, + pixels_per_px) } } @@ -1521,26 +1534,28 @@ impl<'a> PaintContext<'a> { /// Sets a new transient clipping region. Automatically calls /// `remove_transient_clip_if_applicable()` first. pub fn push_transient_clip(&mut self, clip_region: ClippingRegion) { + let scale = self.screen_pixels_per_px(); self.remove_transient_clip_if_applicable(); self.draw_push_clip(&clip_region.main); for complex_region in &clip_region.complex { // FIXME(pcwalton): Actually draw a rounded rect. - self.push_rounded_rect_clip(&complex_region.rect.to_nearest_azure_rect(), - &complex_region.radii.to_radii_px()) + self.push_rounded_rect_clip(&complex_region.rect.to_nearest_azure_rect(scale), + &complex_region.radii.to_radii_pixels(scale)) } self.transient_clip = Some(clip_region) } } pub trait ToAzurePoint { - fn to_nearest_azure_point(&self) -> Point2D<AzFloat>; + fn to_nearest_azure_point(&self, pixels_per_px: f32) -> Point2D<AzFloat>; fn to_azure_point(&self) -> Point2D<AzFloat>; } impl ToAzurePoint for Point2D<Au> { - fn to_nearest_azure_point(&self) -> Point2D<AzFloat> { - Point2D::new(self.x.to_nearest_px() as AzFloat, self.y.to_nearest_px() as AzFloat) + fn to_nearest_azure_point(&self, pixels_per_px: f32) -> Point2D<AzFloat> { + Point2D::new(self.x.to_nearest_pixel(pixels_per_px) as AzFloat, + self.y.to_nearest_pixel(pixels_per_px) as AzFloat) } fn to_azure_point(&self) -> Point2D<AzFloat> { Point2D::new(self.x.to_f32_px(), self.y.to_f32_px()) @@ -1548,8 +1563,8 @@ impl ToAzurePoint for Point2D<Au> { } pub trait ToAzureRect { - fn to_nearest_azure_rect(&self) -> Rect<AzFloat>; - fn to_nearest_non_empty_azure_rect(&self) -> Rect<AzFloat>; + fn to_nearest_azure_rect(&self, pixels_per_px: f32) -> Rect<AzFloat>; + fn to_nearest_non_empty_azure_rect(&self, pixels_per_px: f32) -> Rect<AzFloat>; fn to_azure_rect(&self) -> Rect<AzFloat>; } @@ -1557,7 +1572,7 @@ impl ToAzureRect for Rect<Au> { /// Round rects to pixel coordinates, maintaining the invariant of non-overlap, /// assuming that before rounding rects don't overlap. - fn to_nearest_azure_rect(&self) -> Rect<AzFloat> { + fn to_nearest_azure_rect(&self, pixels_per_px: f32) -> Rect<AzFloat> { // Rounding the top left corner to the nearest pixel with the size rounded // to the nearest pixel multiple would violate the non-overlap condition, // e.g. @@ -1566,8 +1581,8 @@ impl ToAzureRect for Rect<Au> { // 10px×10.0px at (0px,7.0px) & 10px×10.0px at (0px,16.0px), which overlap. // // Instead round each corner to the nearest pixel. - let top_left = self.origin.to_nearest_azure_point(); - let bottom_right = self.bottom_right().to_nearest_azure_point(); + let top_left = self.origin.to_nearest_azure_point(pixels_per_px); + let bottom_right = self.bottom_right().to_nearest_azure_point(pixels_per_px); Rect::new(top_left, Size2D::new((bottom_right.x - top_left.x) as AzFloat, (bottom_right.y - top_left.y) as AzFloat)) } @@ -1577,8 +1592,9 @@ impl ToAzureRect for Rect<Au> { /// 10px×0.6px at 0px,28.56px -> 10px×0px at 0px,29px /// Instead round the top left to the nearest pixel and the size to the nearest pixel /// multiple. It's possible for non-overlapping rects after this rounding to overlap. - fn to_nearest_non_empty_azure_rect(&self) -> Rect<AzFloat> { - Rect::new(self.origin.to_nearest_azure_point(), self.size.to_nearest_azure_size()) + fn to_nearest_non_empty_azure_rect(&self, pixels_per_px: f32) -> Rect<AzFloat> { + Rect::new(self.origin.to_nearest_azure_point(pixels_per_px), + self.size.to_nearest_azure_size(pixels_per_px)) } fn to_azure_rect(&self) -> Rect<AzFloat> { @@ -1586,24 +1602,28 @@ impl ToAzureRect for Rect<Au> { } } +pub trait ToNearestAzureSize { + fn to_nearest_azure_size(&self, pixels_per_px: f32) -> Size2D<AzFloat>; +} + +impl ToNearestAzureSize for Size2D<Au> { + fn to_nearest_azure_size(&self, pixels_per_px: f32) -> Size2D<AzFloat> { + Size2D::new(self.width.to_nearest_pixel(pixels_per_px) as AzFloat, + self.height.to_nearest_pixel(pixels_per_px) as AzFloat) + } +} + pub trait ToAzureSize { - fn to_nearest_azure_size(&self) -> Size2D<AzFloat>; fn to_azure_size(&self) -> Size2D<AzFloat>; } impl ToAzureSize for Size2D<Au> { - fn to_nearest_azure_size(&self) -> Size2D<AzFloat> { - Size2D::new(self.width.to_nearest_px() as AzFloat, self.height.to_nearest_px() as AzFloat) - } fn to_azure_size(&self) -> Size2D<AzFloat> { Size2D::new(self.width.to_f32_px(), self.height.to_f32_px()) } } impl ToAzureSize for AzIntSize { - fn to_nearest_azure_size(&self) -> Size2D<AzFloat> { - Size2D::new(self.width as AzFloat, self.height as AzFloat) - } fn to_azure_size(&self) -> Size2D<AzFloat> { Size2D::new(self.width as AzFloat, self.height as AzFloat) } @@ -1613,46 +1633,34 @@ trait ToAzureIntSize { fn to_azure_int_size(&self) -> Size2D<i32>; } -impl ToAzureIntSize for Size2D<Au> { - fn to_azure_int_size(&self) -> Size2D<i32> { - Size2D::new(self.width.to_nearest_px() as i32, self.height.to_nearest_px() as i32) - } -} - impl ToAzureIntSize for Size2D<AzFloat> { fn to_azure_int_size(&self) -> Size2D<i32> { Size2D::new(self.width as i32, self.height as i32) } } -impl ToAzureIntSize for Size2D<i32> { - fn to_azure_int_size(&self) -> Size2D<i32> { - Size2D::new(self.width, self.height) - } -} - -trait ToSideOffsetsPx { - fn to_float_px(&self) -> SideOffsets2D<AzFloat>; +trait ToSideOffsetsPixels { + fn to_float_pixels(&self, pixels_per_px: f32) -> SideOffsets2D<AzFloat>; } -impl ToSideOffsetsPx for SideOffsets2D<Au> { - fn to_float_px(&self) -> SideOffsets2D<AzFloat> { - SideOffsets2D::new(self.top.to_nearest_px() as AzFloat, - self.right.to_nearest_px() as AzFloat, - self.bottom.to_nearest_px() as AzFloat, - self.left.to_nearest_px() as AzFloat) +impl ToSideOffsetsPixels for SideOffsets2D<Au> { + fn to_float_pixels(&self, pixels_per_px: f32) -> SideOffsets2D<AzFloat> { + SideOffsets2D::new(self.top.to_nearest_pixel(pixels_per_px) as AzFloat, + self.right.to_nearest_pixel(pixels_per_px) as AzFloat, + self.bottom.to_nearest_pixel(pixels_per_px) as AzFloat, + self.left.to_nearest_pixel(pixels_per_px) as AzFloat) } } -trait ToRadiiPx { - fn to_radii_px(&self) -> BorderRadii<AzFloat>; +trait ToRadiiPixels { + fn to_radii_pixels(&self, pixels_per_px: f32) -> BorderRadii<AzFloat>; } -impl ToRadiiPx for BorderRadii<Au> { - fn to_radii_px(&self) -> BorderRadii<AzFloat> { - fn to_nearest_px(x: Au) -> AzFloat { - x.to_nearest_px() as AzFloat - } +impl ToRadiiPixels for BorderRadii<Au> { + fn to_radii_pixels(&self, pixels_per_px: f32) -> BorderRadii<AzFloat> { + let to_nearest_px = |x: Au| -> AzFloat { + x.to_nearest_pixel(pixels_per_px) as AzFloat + }; BorderRadii { top_left: Size2D { width: to_nearest_px(self.top_left.width), @@ -1748,18 +1756,20 @@ trait DrawTargetExtensions { /// |################################| /// +--------------------------------+ /// ``` - fn create_rectangular_border_path<T>(&self, outer_rect: &T, inner_rect: &T) - -> Path - where T: ToAzureRect; + fn create_rectangular_border_path(&self, + outer_rect: &Rect<Au>, + inner_rect: &Rect<Au>, + pixels_per_px: f32) -> Path; /// Creates and returns a path that represents a rectangle. - fn create_rectangular_path(&self, rect: &Rect<Au>) -> Path; + fn create_rectangular_path(&self, rect: &Rect<Au>, pixels_per_px: f32) -> Path; } impl DrawTargetExtensions for DrawTarget { - fn create_rectangular_border_path<T>(&self, outer_rect: &T, inner_rect: &T) - -> Path - where T: ToAzureRect { + fn create_rectangular_border_path(&self, + outer_rect: &Rect<Au>, + inner_rect: &Rect<Au>, + pixels_per_px: f32) -> Path { // +-----------+ // |2 |1 // | | @@ -1772,7 +1782,8 @@ impl DrawTargetExtensions for DrawTarget { // +-----------+ // 3 4 - let (outer_rect, inner_rect) = (outer_rect.to_nearest_azure_rect(), inner_rect.to_nearest_azure_rect()); + let outer_rect = outer_rect.to_nearest_azure_rect(pixels_per_px); + let inner_rect = inner_rect.to_nearest_azure_rect(pixels_per_px); let path_builder = self.create_path_builder(); path_builder.move_to(Point2D::new(outer_rect.max_x(), outer_rect.origin.y)); // 1 path_builder.line_to(Point2D::new(outer_rect.origin.x, outer_rect.origin.y)); // 2 @@ -1787,11 +1798,11 @@ impl DrawTargetExtensions for DrawTarget { path_builder.finish() } - fn create_rectangular_path(&self, rect: &Rect<Au>) -> Path { + fn create_rectangular_path(&self, rect: &Rect<Au>, pixels_per_px: f32) -> Path { // Explicitly round to the nearest non-empty rect because when drawing // box-shadow the rect height can be between 0.5px & 1px and could // otherwise round to an empty rect. - let rect = rect.to_nearest_non_empty_azure_rect(); + let rect = rect.to_nearest_non_empty_azure_rect(pixels_per_px); let path_builder = self.create_path_builder(); path_builder.move_to(rect.origin); diff --git a/components/util/geometry.rs b/components/util/geometry.rs index ea88b0d1713..5b6d8a0852a 100644 --- a/components/util/geometry.rs +++ b/components/util/geometry.rs @@ -237,6 +237,11 @@ impl Au { } #[inline] + pub fn to_nearest_pixel(self, pixels_per_px: f32) -> f32 { + ((self.0 as f32) / (AU_PER_PX as f32) * pixels_per_px).round() / pixels_per_px + } + + #[inline] pub fn to_f32_px(self) -> f32 { (self.0 as f32) / (AU_PER_PX as f32) } diff --git a/tests/ref/basic.list b/tests/ref/basic.list index d899e60d1cf..404adece8de 100644 --- a/tests/ref/basic.list +++ b/tests/ref/basic.list @@ -285,6 +285,7 @@ flaky_cpu == linebreak_simple_a.html linebreak_simple_b.html == percentage_height_float_a.html percentage_height_float_ref.html == percentage_height_root.html percentage_height_root_ref.html == percentage_width_inline_block_a.html percentage_width_inline_block_ref.html +device-pixel-ratio=2 != pixel_snapping_border_a.html pixel_snapping_border_ref.html == png_rgba_colorspace_a.html png_rgba_colorspace_b.html == position_abs_cb_with_non_cb_kid_a.html position_abs_cb_with_non_cb_kid_b.html == position_abs_height_width_a.html position_abs_height_width_b.html diff --git a/tests/ref/pixel_snapping_border_a.html b/tests/ref/pixel_snapping_border_a.html new file mode 100644 index 00000000000..78d76b1b353 --- /dev/null +++ b/tests/ref/pixel_snapping_border_a.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="UTF-8"> + <title>Pixel snapping border test</title> + <style> + div { + height: 101px; + width: 101px; + border: 0.5px solid black; + } + </style> + </head> + <body> + <div></div> + </body> +</html> + diff --git a/tests/ref/pixel_snapping_border_ref.html b/tests/ref/pixel_snapping_border_ref.html new file mode 100644 index 00000000000..483046f65cf --- /dev/null +++ b/tests/ref/pixel_snapping_border_ref.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="UTF-8"> + <title>Pixel snapping border reference</title> + <style> + div { + height: 100px; + width: 100px; + border: 1px solid black; + } + </style> + </head> + <body> + <div></div> + </body> +</html> + |