зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
9483eb9c71
Коммит
470d4c6138
|
@ -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
Двоичные данные
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
|
||||
|
|
Двоичные данные
gfx/wr/wrench/reftests/text/shadow-transforms.png
Двоичные данные
gfx/wr/wrench/reftests/text/shadow-transforms.png
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 92 KiB После Ширина: | Высота: | Размер: 94 KiB |
Загрузка…
Ссылка в новой задаче