Bug 1605642 - Fix glyph distortion during animations. r=lsalzman

When we animate text, we rasterize the glyphs in an arbitrary local
space once, and scale them during the animation. Some glyphs may be
pressed against the edge of the texture, resulting in artifacts due to
how the sampling works in the shader. This patch fixes the sampling
issues by padding glyph textures with an extra transparent pixel border.
This only applies to glyphs that are rasterized in local space.

This patch does not add the extra padding for Mac because it is already
padding its glyphs for Mac-specific reasons, and does not appear to be
as suspectible to the problem.

Differential Revision: https://phabricator.services.mozilla.com/D74457
This commit is contained in:
Andrew Osmond 2020-05-08 20:23:41 +00:00
Родитель 9483eb9c71
Коммит 470d4c6138
7 изменённых файлов: 99 добавлений и 46 удалений

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

@ -418,6 +418,10 @@ pub struct FontInstance {
pub flags: FontInstanceFlags,
pub color: ColorU,
pub transform_glyphs: bool,
// If true, add padding to the rasterized glyph buffer. This is
// useful when one anticipates the glyph will need to be scaled
// when rendered.
pub texture_padding: bool,
// The font size is in *device* pixels, not logical pixels.
// It is stored as an Au since we need sub-pixel sizes, but
// can't store as a f32 due to use of this type as a hash key.
@ -460,6 +464,7 @@ impl FontInstance {
FontInstance {
transform: FontTransform::identity(),
transform_glyphs: false,
texture_padding: false,
color,
size: base.size,
base,
@ -474,6 +479,7 @@ impl FontInstance {
FontInstance {
transform: FontTransform::identity(),
transform_glyphs: false,
texture_padding: false,
color: ColorU::new(0, 0, 0, 255),
size: base.size,
render_mode: base.render_mode,

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

@ -868,14 +868,23 @@ impl FontContext {
}
_ => panic!("Unsupported mode"),
};
let mut final_buffer = vec![0u8; actual_width * actual_height * 4];
// If we need padding, we will need to expand the buffer size.
let (buffer_width, buffer_height, padding) = if font.texture_padding {
(actual_width + 2, actual_height + 2, 1)
} else {
(actual_width, actual_height, 0)
};
let mut final_buffer = vec![0u8; buffer_width * buffer_height * 4];
// Extract the final glyph from FT format into BGRA8 format, which is
// what WR expects.
let subpixel_bgr = font.flags.contains(FontInstanceFlags::SUBPIXEL_BGR);
let mut src_row = bitmap.buffer;
let mut dest: usize = 0;
while dest < final_buffer.len() {
let mut dest = 4 * padding * (padding + buffer_width);
let actual_end = final_buffer.len() - 4 * padding * (buffer_width + 1);
while dest < actual_end {
let mut src = src_row;
let row_end = dest + actual_width * 4;
match pixel_mode {
@ -947,7 +956,14 @@ impl FontContext {
_ => panic!("Unsupported mode"),
}
src_row = unsafe { src_row.offset(bitmap.pitch as isize) };
dest = row_end;
dest = row_end + 8 * padding;
}
if font.texture_padding {
left -= padding as i32;
top += padding as i32;
actual_width = buffer_width;
actual_height = buffer_height;
}
match format {

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

@ -413,52 +413,78 @@ impl FontContext {
fn convert_to_bgra(
&self,
pixels: &[u8],
width: usize,
height: usize,
texture_type: dwrote::DWRITE_TEXTURE_TYPE,
render_mode: FontRenderMode,
bitmaps: bool,
subpixel_bgr: bool,
texture_padding: bool,
) -> Vec<u8> {
let (buffer_width, buffer_height, padding) = if texture_padding {
(width + 2, height + 2, 1)
} else {
(width, height, 0)
};
let buffer_length = buffer_width * buffer_height * 4;
let mut bgra_pixels: Vec<u8> = vec![0; buffer_length];
match (texture_type, render_mode, bitmaps) {
(dwrote::DWRITE_TEXTURE_ALIASED_1x1, _, _) => {
let mut bgra_pixels: Vec<u8> = vec![0; pixels.len() * 4];
for i in 0 .. pixels.len() {
let alpha = pixels[i];
bgra_pixels[i * 4 + 0] = alpha;
bgra_pixels[i * 4 + 1] = alpha;
bgra_pixels[i * 4 + 2] = alpha;
bgra_pixels[i * 4 + 3] = alpha;
assert!(width * height == pixels.len());
let mut i = 0;
for row in padding .. height + padding {
let row_offset = row * buffer_width;
for col in padding .. width + padding {
let offset = (row_offset + col) * 4;
let alpha = pixels[i];
i += 1;
bgra_pixels[offset + 0] = alpha;
bgra_pixels[offset + 1] = alpha;
bgra_pixels[offset + 2] = alpha;
bgra_pixels[offset + 3] = alpha;
}
}
bgra_pixels
}
(_, FontRenderMode::Subpixel, false) => {
let length = pixels.len() / 3;
let mut bgra_pixels: Vec<u8> = vec![0; length * 4];
for i in 0 .. length {
let (mut r, g, mut b) = (pixels[i * 3 + 0], pixels[i * 3 + 1], pixels[i * 3 + 2]);
if subpixel_bgr {
mem::swap(&mut r, &mut b);
assert!(width * height * 3 == pixels.len());
let mut i = 0;
for row in padding .. height + padding {
let row_offset = row * buffer_width;
for col in padding .. width + padding {
let offset = (row_offset + col) * 4;
let (mut r, g, mut b) = (pixels[i + 0], pixels[i + 1], pixels[i + 2]);
if subpixel_bgr {
mem::swap(&mut r, &mut b);
}
i += 3;
bgra_pixels[offset + 0] = b;
bgra_pixels[offset + 1] = g;
bgra_pixels[offset + 2] = r;
bgra_pixels[offset + 3] = 0xff;
}
bgra_pixels[i * 4 + 0] = b;
bgra_pixels[i * 4 + 1] = g;
bgra_pixels[i * 4 + 2] = r;
bgra_pixels[i * 4 + 3] = 0xff;
}
bgra_pixels
}
_ => {
let length = pixels.len() / 3;
let mut bgra_pixels: Vec<u8> = vec![0; length * 4];
for i in 0 .. length {
// Only take the G channel, as its closest to D2D
let alpha = pixels[i * 3 + 1] as u8;
bgra_pixels[i * 4 + 0] = alpha;
bgra_pixels[i * 4 + 1] = alpha;
bgra_pixels[i * 4 + 2] = alpha;
bgra_pixels[i * 4 + 3] = alpha;
assert!(width * height * 3 == pixels.len());
let mut i = 0;
for row in padding .. height + padding {
let row_offset = row * buffer_width;
for col in padding .. width + padding {
let offset = (row_offset + col) * 4;
// Only take the G channel, as its closest to D2D
let alpha = pixels[i + 1] as u8;
i += 3;
bgra_pixels[offset + 0] = alpha;
bgra_pixels[offset + 1] = alpha;
bgra_pixels[offset + 2] = alpha;
bgra_pixels[offset + 3] = alpha;
}
}
bgra_pixels
}
}
};
bgra_pixels
}
pub fn prepare_font(font: &mut FontInstance) {
@ -533,8 +559,10 @@ impl FontContext {
}
let pixels = analysis.create_alpha_texture(texture_type, bounds).or(Err(GlyphRasterError::LoadFailed))?;
let mut bgra_pixels = self.convert_to_bgra(&pixels, texture_type, font.render_mode, bitmaps,
font.flags.contains(FontInstanceFlags::SUBPIXEL_BGR));
let mut bgra_pixels = self.convert_to_bgra(&pixels, width as usize, height as usize,
texture_type, font.render_mode, bitmaps,
font.flags.contains(FontInstanceFlags::SUBPIXEL_BGR),
font.texture_padding);
let FontInstancePlatformOptions { gamma, contrast, cleartype_level, .. } =
font.platform_options.unwrap_or_default();
@ -561,11 +589,12 @@ impl FontContext {
font.get_glyph_format()
};
let padding = if font.texture_padding { 1 } else { 0 };
Ok(RasterizedGlyph {
left: bounds.left as f32,
top: -bounds.top as f32,
width,
height,
left: (bounds.left - padding) as f32,
top: (-bounds.top + padding) as f32,
width: width + padding * 2,
height: height + padding * 2,
scale: (if bitmaps { y_scale.recip() } else { 1.0 }) as f32,
format,
bytes: bgra_pixels,

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

@ -249,14 +249,14 @@ impl TextRunPrimitive {
// Check there is a valid transform that doesn't exceed the font size limit.
// Ensure the font is supposed to be rasterized in screen-space.
// Only support transforms that can be coerced to simple 2D transforms.
let (use_subpixel_aa, transform_glyphs, oversized) = if raster_space != RasterSpace::Screen ||
let (use_subpixel_aa, transform_glyphs, texture_padding, oversized) = if raster_space != RasterSpace::Screen ||
transform.has_perspective_component() || !transform.has_2d_inverse()
{
(false, false, device_font_size > FONT_SIZE_LIMIT)
(false, false, true, device_font_size > FONT_SIZE_LIMIT)
} else if transform.exceeds_2d_scale((FONT_SIZE_LIMIT / device_font_size) as f64) {
(false, false, true)
(false, false, true, true)
} else {
(true, !transform.is_simple_2d_translation(), false)
(true, !transform.is_simple_2d_translation(), false, false)
};
let font_transform = if transform_glyphs {
@ -312,12 +312,14 @@ impl TextRunPrimitive {
let cache_dirty =
self.used_font.transform != font_transform ||
self.used_font.size != specified_font.size ||
self.used_font.transform_glyphs != transform_glyphs;
self.used_font.transform_glyphs != transform_glyphs ||
self.used_font.texture_padding != texture_padding;
// Construct used font instance from the specified font instance
self.used_font = FontInstance {
transform: font_transform,
transform_glyphs,
texture_padding,
size: specified_font.size,
..specified_font.clone()
};

Двоичные данные
gfx/wr/wrench/reftests/text/raster-space.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 61 KiB

После

Ширина:  |  Высота:  |  Размер: 63 KiB

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

@ -7,7 +7,7 @@
!= shadow-cover-2.yaml blank.yaml
skip_on(android,device) fuzzy(1,3) == shadow.yaml shadow-ref.yaml # Fails on Pixel2
== shadow-huge.yaml shadow-huge-ref.yaml
fuzzy(1,1) == shadow-huge.yaml shadow-huge-ref.yaml
!= shadow-cover-1.yaml shadow-cover-2.yaml
!= shadow-many.yaml shadow.yaml
!= shadow-complex.yaml shadow-many.yaml

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 92 KiB

После

Ширина:  |  Высота:  |  Размер: 94 KiB