mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 12:51:06 +00:00
Bug 1625606 - Use Skia's mac font smoothing handling. r=aosmond
Currently we try to alternate between light-on-dark and dark-on-light text rendering masks on macOS depending on text colors. Historically, this matched earlier versions of macOS reasonably well. In successive versions of macOS, however, this approximation appears to have broken down and no longer gives correct results. Given that recent macOS versions no longer promotes font-smoothing at all, it seems more reasonable to match Skia's font smoothing handling because it appears to better work with these more recent versions while requiring us to have less special code to do so. Differential Revision: https://phabricator.services.mozilla.com/D229444
This commit is contained in:
parent
1415e9c6a1
commit
ccba550402
@ -4336,27 +4336,17 @@ static DeviceColor QuantizePreblendColor(const DeviceColor& aColor,
|
||||
int32_t r = int32_t(aColor.r * 255.0f + 0.5f);
|
||||
int32_t g = int32_t(aColor.g * 255.0f + 0.5f);
|
||||
int32_t b = int32_t(aColor.b * 255.0f + 0.5f);
|
||||
// Ensure that even if two values would normally quantize to the same bucket,
|
||||
// that the reference value within the bucket still allows for accurate
|
||||
// determination of whether light-on-dark or dark-on-light rasterization will
|
||||
// be used (as on macOS).
|
||||
bool lightOnDark = r >= 85 && g >= 85 && b >= 85 && r + g + b >= 2 * 255;
|
||||
// Skia only uses the high 3 bits of each color component to cache preblend
|
||||
// ramp tables.
|
||||
constexpr int32_t lumBits = 3;
|
||||
constexpr int32_t ceilMask = (1 << (8 - lumBits)) - 1;
|
||||
constexpr int32_t floorMask = ((1 << lumBits) - 1) << (8 - lumBits);
|
||||
if (!aUseSubpixelAA) {
|
||||
// If not using subpixel AA, then quantize only the luminance, stored in the
|
||||
// G channel.
|
||||
g = (r * 54 + g * 183 + b * 19) >> 8;
|
||||
g |= ceilMask;
|
||||
// Still distinguish between light and dark in the key.
|
||||
r = b = lightOnDark ? 255 : 0;
|
||||
} else if (lightOnDark) {
|
||||
r |= ceilMask;
|
||||
g |= ceilMask;
|
||||
b |= ceilMask;
|
||||
g &= floorMask;
|
||||
r = g;
|
||||
b = g;
|
||||
} else {
|
||||
r &= floorMask;
|
||||
g &= floorMask;
|
||||
@ -4415,8 +4405,7 @@ bool SharedContextWebgl::DrawGlyphsAccel(ScaledFont* aFont,
|
||||
#endif
|
||||
|
||||
// If the font has bitmaps, use the color directly. Otherwise, the texture
|
||||
// will hold a grayscale mask, so encode the key's subpixel and light-or-dark
|
||||
// state in the color.
|
||||
// holds a grayscale mask, so encode the key's subpixel state in the color.
|
||||
const Matrix& currentTransform = mCurrentTarget->GetTransform();
|
||||
IntPoint quantizeScale = QuantizeScale(aFont, currentTransform);
|
||||
Matrix quantizeTransform = currentTransform;
|
||||
|
@ -141,8 +141,6 @@
|
||||
|
||||
#define SK_USE_FREETYPE_EMBOLDEN
|
||||
|
||||
#define SK_IGNORE_MAC_BLENDING_MATCH_FIX
|
||||
|
||||
#ifndef MOZ_IMPLICIT
|
||||
# ifdef MOZ_CLANG_PLUGIN
|
||||
# define MOZ_IMPLICIT __attribute__((annotate("moz_implicit")))
|
||||
|
@ -280,8 +280,6 @@ public:
|
||||
kBaselineSnap_Flag = 0x2000,
|
||||
|
||||
kNeedsForegroundColor_Flag = 0x4000,
|
||||
|
||||
kLightOnDark_Flag = 0x8000, // Moz + Mac only, used to distinguish different mask dilations
|
||||
};
|
||||
|
||||
// computed values
|
||||
|
@ -177,8 +177,7 @@ SkScalerContext_Mac::Offscreen::Offscreen(SkColor foregroundColor)
|
||||
CGRGBPixel* SkScalerContext_Mac::Offscreen::getCG(const SkScalerContext_Mac& context,
|
||||
const SkGlyph& glyph, CGGlyph glyphID,
|
||||
size_t* rowBytesPtr,
|
||||
bool generateA8FromLCD,
|
||||
bool lightOnDark) {
|
||||
bool generateA8FromLCD) {
|
||||
if (!fRGBSpace) {
|
||||
//It doesn't appear to matter what color space is specified.
|
||||
//Regular blends and antialiased text are always (s*a + d*(1-a))
|
||||
@ -242,8 +241,7 @@ CGRGBPixel* SkScalerContext_Mac::Offscreen::getCG(const SkScalerContext_Mac& con
|
||||
|
||||
if (SkMask::kARGB32_Format != glyph.maskFormat()) {
|
||||
// Draw black on white to create mask. (Special path exists to speed this up in CG.)
|
||||
// If light-on-dark is requested, draw white on black.
|
||||
CGContextSetGrayFillColor(fCG.get(), lightOnDark ? 1.0f : 0.0f, 1.0f);
|
||||
CGContextSetGrayFillColor(fCG.get(), 0.0f, 1.0f);
|
||||
} else {
|
||||
CGContextSetFillColorWithColor(fCG.get(), fCGForegroundColor.get());
|
||||
}
|
||||
@ -270,7 +268,7 @@ CGRGBPixel* SkScalerContext_Mac::Offscreen::getCG(const SkScalerContext_Mac& con
|
||||
|
||||
// Erase to white (or transparent black if it's a color glyph, to not composite against white).
|
||||
// For light-on-dark, instead erase to black.
|
||||
uint32_t bgColor = (!glyph.isColor()) ? (lightOnDark ? 0xFF000000 : 0xFFFFFFFF) : 0x00000000;
|
||||
uint32_t bgColor = (!glyph.isColor()) ? 0xFFFFFFFF : 0x00000000;
|
||||
sk_memset_rect32(image, bgColor, glyph.width(), glyph.height(), rowBytes);
|
||||
|
||||
float subX = 0;
|
||||
@ -473,11 +471,10 @@ void SkScalerContext_Mac::generateImage(const SkGlyph& glyph, void* imageBuffer)
|
||||
|
||||
// FIXME: lcd smoothed un-hinted rasterization unsupported.
|
||||
bool requestSmooth = fRec.getHinting() != SkFontHinting::kNone;
|
||||
bool lightOnDark = (fRec.fFlags & SkScalerContext::kLightOnDark_Flag) != 0;
|
||||
|
||||
// Draw the glyph
|
||||
size_t cgRowBytes;
|
||||
CGRGBPixel* cgPixels = fOffscreen.getCG(*this, glyph, cgGlyph, &cgRowBytes, requestSmooth, lightOnDark);
|
||||
CGRGBPixel* cgPixels = fOffscreen.getCG(*this, glyph, cgGlyph, &cgRowBytes, requestSmooth);
|
||||
if (cgPixels == nullptr) {
|
||||
return;
|
||||
}
|
||||
@ -498,16 +495,10 @@ void SkScalerContext_Mac::generateImage(const SkGlyph& glyph, void* imageBuffer)
|
||||
CGRGBPixel* addr = cgPixels;
|
||||
for (int y = 0; y < glyph.height(); ++y) {
|
||||
for (int x = 0; x < glyph.width(); ++x) {
|
||||
int r = linear[(addr[x] >> 16) & 0xFF];
|
||||
int g = linear[(addr[x] >> 8) & 0xFF];
|
||||
int b = linear[(addr[x] >> 0) & 0xFF];
|
||||
// If light-on-dark was requested, the mask is drawn inverted.
|
||||
if (lightOnDark) {
|
||||
r = 255 - r;
|
||||
g = 255 - g;
|
||||
b = 255 - b;
|
||||
}
|
||||
addr[x] = (r << 16) | (g << 8) | b;
|
||||
int r = (addr[x] >> 16) & 0xFF;
|
||||
int g = (addr[x] >> 8) & 0xFF;
|
||||
int b = (addr[x] >> 0) & 0xFF;
|
||||
addr[x] = (linear[r] << 16) | (linear[g] << 8) | linear[b];
|
||||
}
|
||||
addr = SkTAddOffset<CGRGBPixel>(addr, cgRowBytes);
|
||||
}
|
||||
|
@ -55,8 +55,7 @@ private:
|
||||
Offscreen(SkColor foregroundColor);
|
||||
|
||||
CGRGBPixel* getCG(const SkScalerContext_Mac& context, const SkGlyph& glyph,
|
||||
CGGlyph glyphID, size_t* rowBytesPtr, bool generateA8FromLCD,
|
||||
bool lightOnDark);
|
||||
CGGlyph glyphID, size_t* rowBytesPtr, bool generateA8FromLCD);
|
||||
|
||||
private:
|
||||
enum {
|
||||
|
@ -980,21 +980,6 @@ void SkTypeface_Mac::onFilterRec(SkScalerContextRec* rec) const {
|
||||
rec->fMaskFormat = SkMask::kARGB32_Format;
|
||||
}
|
||||
|
||||
// Smoothing will be used if the format is either LCD or if there is hinting.
|
||||
// In those cases, we need to choose the proper dilation mask based on the color.
|
||||
if (rec->fMaskFormat == SkMask::kLCD16_Format ||
|
||||
(rec->fMaskFormat == SkMask::kA8_Format && rec->getHinting() != SkFontHinting::kNone)) {
|
||||
SkColor color = rec->getLuminanceColor();
|
||||
int r = SkColorGetR(color);
|
||||
int g = SkColorGetG(color);
|
||||
int b = SkColorGetB(color);
|
||||
// Choose whether to draw using a light-on-dark mask based on observed
|
||||
// color/luminance thresholds that CoreText uses.
|
||||
if (r >= 85 && g >= 85 && b >= 85 && r + g + b >= 2 * 255) {
|
||||
rec->fFlags |= SkScalerContext::kLightOnDark_Flag;
|
||||
}
|
||||
}
|
||||
|
||||
// Unhinted A8 masks (those not derived from LCD masks) must respect SK_GAMMA_APPLY_TO_A8.
|
||||
// All other masks can use regular gamma.
|
||||
if (SkMask::kA8_Format == rec->fMaskFormat && SkFontHinting::kNone == rec->getHinting()) {
|
||||
@ -1003,7 +988,6 @@ void SkTypeface_Mac::onFilterRec(SkScalerContextRec* rec) const {
|
||||
rec->ignorePreBlend();
|
||||
#endif
|
||||
} else {
|
||||
#ifndef SK_IGNORE_MAC_BLENDING_MATCH_FIX
|
||||
SkColor color = rec->getLuminanceColor();
|
||||
if (smoothBehavior == SkCTFontSmoothBehavior::some) {
|
||||
// CoreGraphics smoothed text without subpixel coverage blitting goes from a gamma of
|
||||
@ -1021,7 +1005,6 @@ void SkTypeface_Mac::onFilterRec(SkScalerContextRec* rec) const {
|
||||
SkColorGetB(color) * 3/4);
|
||||
}
|
||||
rec->setLuminanceColor(color);
|
||||
#endif
|
||||
|
||||
// CoreGraphics dialates smoothed text to provide contrast.
|
||||
rec->setContrast(0);
|
||||
|
@ -24,7 +24,7 @@ fuzzy-if(!useDrawSnapshot,14-14,44-95) == 1524353.html 1524353-ref.html
|
||||
== bug1523410-translate-scale-snap.html bug1523410-translate-scale-snap-ref.html
|
||||
== 1523080.html 1523080-ref.html
|
||||
== 1616444-same-color-different-paths.html 1616444-same-color-different-paths-ref.html
|
||||
skip-if(useDrawSnapshot||Android) fuzzy-if(winWidget,54-94,2713-3419) fuzzy-if(cocoaWidget,24-24,1190-1200) pref(apz.allow_zooming,true) == picture-caching-on-async-zoom.html picture-caching-on-async-zoom.html?ref
|
||||
skip-if(useDrawSnapshot||Android) fuzzy-if(winWidget,54-94,2713-3419) fuzzy-if(cocoaWidget,22-24,1100-1200) pref(apz.allow_zooming,true) == picture-caching-on-async-zoom.html picture-caching-on-async-zoom.html?ref
|
||||
pref(apz.allow_zooming,true) fails-if(useDrawSnapshot) fuzzy-if(!useDrawSnapshot,0-244,0-4285) == 1662062-1-no-blurry.html 1662062-1-ref.html
|
||||
# Bug 1715676: nsBulletFrame has been removed and the new rendering does not use PushRoundedRect that this test is for:
|
||||
# == 1681610.html 1681610-ref.html
|
||||
|
@ -127,12 +127,6 @@ lazy_static! {
|
||||
static ref FONT_SMOOTHING_MODE: Option<FontRenderMode> = determine_font_smoothing_mode();
|
||||
}
|
||||
|
||||
fn should_use_white_on_black(color: ColorU) -> bool {
|
||||
let (r, g, b) = (color.r as u32, color.g as u32, color.b as u32);
|
||||
// These thresholds were determined on 10.12 by observing what CG does.
|
||||
r >= 85 && g >= 85 && b >= 85 && r + g + b >= 2 * 255
|
||||
}
|
||||
|
||||
fn get_glyph_metrics(
|
||||
ct_font: &CTFont,
|
||||
transform: Option<&CGAffineTransform>,
|
||||
@ -450,13 +444,22 @@ impl FontContext {
|
||||
render_mode: FontRenderMode,
|
||||
color: ColorU,
|
||||
) {
|
||||
let ColorU {r, g, b, a} = color;
|
||||
let smooth_color = match *FONT_SMOOTHING_MODE {
|
||||
// Use Skia's gamma approximation for subpixel smoothing of 3/4.
|
||||
Some(FontRenderMode::Subpixel) => ColorU::new(r - r / 4, g - g / 4, b - b / 4, a),
|
||||
// Use Skia's gamma approximation for grayscale smoothing of 1/2.
|
||||
Some(FontRenderMode::Alpha) => ColorU::new(r / 2, g / 2, b / 2, a),
|
||||
_ => color,
|
||||
};
|
||||
|
||||
// Then convert back to gamma corrected values.
|
||||
match render_mode {
|
||||
FontRenderMode::Alpha => {
|
||||
self.gamma_lut.preblend_grayscale(pixels, color);
|
||||
self.gamma_lut.preblend_grayscale(pixels, smooth_color);
|
||||
}
|
||||
FontRenderMode::Subpixel => {
|
||||
self.gamma_lut.preblend(pixels, color);
|
||||
self.gamma_lut.preblend(pixels, smooth_color);
|
||||
}
|
||||
_ => {} // Again, give mono untouched since only the alpha matters.
|
||||
}
|
||||
@ -512,23 +515,13 @@ impl FontContext {
|
||||
}
|
||||
FontRenderMode::Alpha => {
|
||||
font.color = if font.flags.contains(FontInstanceFlags::FONT_SMOOTHING) {
|
||||
// Only the G channel is used to index grayscale tables,
|
||||
// so use R and B to preserve light/dark determination.
|
||||
let ColorU { g, a, .. } = font.color.luminance_color().quantized_ceil();
|
||||
let rb = if should_use_white_on_black(font.color) { 255 } else { 0 };
|
||||
ColorU::new(rb, g, rb, a)
|
||||
font.color.luminance_color().quantize()
|
||||
} else {
|
||||
ColorU::new(255, 255, 255, 255)
|
||||
};
|
||||
}
|
||||
FontRenderMode::Subpixel => {
|
||||
// Quantization may change the light/dark determination, so quantize in the
|
||||
// direction necessary to respect the threshold.
|
||||
font.color = if should_use_white_on_black(font.color) {
|
||||
font.color.quantized_ceil()
|
||||
} else {
|
||||
font.color.quantized_floor()
|
||||
};
|
||||
font.color = font.color.quantize();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -641,20 +634,15 @@ impl FontContext {
|
||||
// table data, require the current font color to determine the output
|
||||
// color. For such fonts we must thus supply the current font color just
|
||||
// in case it is necessary.
|
||||
let use_white_on_black = should_use_white_on_black(font.color);
|
||||
let use_font_smoothing = font.flags.contains(FontInstanceFlags::FONT_SMOOTHING);
|
||||
let (antialias, smooth, text_color, bg_color, invert) = match glyph_type {
|
||||
GlyphType::Bitmap => (true, false, ColorF::from(font.color), ColorF::TRANSPARENT, false),
|
||||
let (antialias, smooth, text_color, bg_color) = match glyph_type {
|
||||
GlyphType::Bitmap => (true, false, ColorF::from(font.color), ColorF::TRANSPARENT),
|
||||
GlyphType::Vector => {
|
||||
match (font.render_mode, use_font_smoothing) {
|
||||
(FontRenderMode::Subpixel, _) |
|
||||
(FontRenderMode::Alpha, true) => if use_white_on_black {
|
||||
(true, true, ColorF::WHITE, ColorF::BLACK, false)
|
||||
} else {
|
||||
(true, true, ColorF::BLACK, ColorF::WHITE, true)
|
||||
},
|
||||
(FontRenderMode::Alpha, false) => (true, false, ColorF::BLACK, ColorF::WHITE, true),
|
||||
(FontRenderMode::Mono, _) => (false, false, ColorF::BLACK, ColorF::WHITE, true),
|
||||
(FontRenderMode::Alpha, true) => (true, true, ColorF::BLACK, ColorF::WHITE),
|
||||
(FontRenderMode::Alpha, false) => (true, false, ColorF::BLACK, ColorF::WHITE),
|
||||
(FontRenderMode::Mono, _) => (false, false, ColorF::BLACK, ColorF::WHITE),
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -757,11 +745,9 @@ impl FontContext {
|
||||
}
|
||||
|
||||
for pixel in rasterized_pixels.chunks_mut(4) {
|
||||
if invert {
|
||||
pixel[0] = 255 - pixel[0];
|
||||
pixel[1] = 255 - pixel[1];
|
||||
pixel[2] = 255 - pixel[2];
|
||||
}
|
||||
pixel[0] = 255 - pixel[0];
|
||||
pixel[1] = 255 - pixel[1];
|
||||
pixel[2] = 255 - pixel[2];
|
||||
|
||||
// Set alpha to the value of the green channel. For grayscale
|
||||
// text, all three channels have the same value anyway.
|
||||
|
@ -20,7 +20,7 @@ fuzzy(2,405) fuzzy-if(platform(swgl),2,1510) == split-batch.yaml split-batch-ref
|
||||
# Next 3 tests affected by bug 1548099 on Android
|
||||
skip_on(android) == shadow-red.yaml shadow-red-ref.yaml
|
||||
skip_on(android) fuzzy(1,999) fuzzy-if(platform(swgl),2,1324) == shadow-grey.yaml shadow-grey-ref.yaml
|
||||
skip_on(android) fuzzy(1,828) fuzzy-if(platform(swgl),2,1538) == shadow-grey-transparent.yaml shadow-grey-ref.yaml
|
||||
skip_on(android) fuzzy(1,834) fuzzy-if(platform(swgl),2,1538) == shadow-grey-transparent.yaml shadow-grey-ref.yaml
|
||||
== subtle-shadow.yaml subtle-shadow-ref.yaml
|
||||
fuzzy(1,64) == shadow-atomic.yaml shadow-atomic-ref.yaml
|
||||
fuzzy(1,64) == shadow-clip-rect.yaml shadow-atomic-ref.yaml
|
||||
|
@ -31,7 +31,7 @@ random-if(cocoaWidget) == subpixel-1.html about:blank # see bug 1192616, re-enab
|
||||
== text-rtl-alignment-test.html text-rtl-alignment-ref.html
|
||||
|
||||
fuzzy-if(winWidget,0-1,0-256) == text-horzline-with-bottom.html text-horzline.html
|
||||
fuzzy-if(winWidget,0-1,0-256) fails-if(cocoaWidget) == text-horzline-with-top.html text-horzline.html
|
||||
fuzzy-if(winWidget,0-1,0-256) == text-horzline-with-top.html text-horzline.html
|
||||
|
||||
!= text-big-stroke.html text-blank.html
|
||||
!= text-big-stroke.html text-big-fill.html
|
||||
|
@ -154,9 +154,9 @@ HTTP(..) == font-redirect.html order-1-ref.html
|
||||
== dynamic-duplicate-rule-1c.html dynamic-duplicate-rule-1-ref.html
|
||||
|
||||
# Test for COLR and CPAL support
|
||||
fuzzy-if(cocoaWidget,198-198,172-172) == color-1a.html color-1-ref.html
|
||||
fuzzy-if(cocoaWidget,154-198,172-172) == color-1a.html color-1-ref.html
|
||||
!= color-1a.html color-1-notref.html
|
||||
fuzzy-if(cocoaWidget,198-198,172-172) == color-1b.html color-1-ref.html
|
||||
fuzzy-if(cocoaWidget,154-198,172-172) == color-1b.html color-1-ref.html
|
||||
== color-2a.html color-2-ref.html
|
||||
!= color-2a.html color-2-notref.html
|
||||
|
||||
|
@ -25,7 +25,7 @@ fuzzy-if(winWidget,47-138,260-319) == simple-bidi.svg simple-bidi-ref.html
|
||||
== simple-dx-rtl-2.svg simple-dx-rtl-2-ref.svg
|
||||
|
||||
== simple-fill-color-dynamic.svg simple-fill-color-dynamic-ref.svg
|
||||
fuzzy-if(winWidget,47-129,221-254) fuzzy-if(cocoaWidget,23-65,195-196) == simple-fill-color.svg simple-fill-color-ref.html
|
||||
fuzzy-if(winWidget,47-129,221-254) fuzzy-if(cocoaWidget,0-65,0-196) == simple-fill-color.svg simple-fill-color-ref.html
|
||||
== simple-fill-gradient.svg simple-fill-gradient-ref.svg
|
||||
== simple-fill-none.svg simple.svg
|
||||
== simple-pointer-events.svg simple.svg
|
||||
|
@ -1,4 +1,4 @@
|
||||
[first-line-bidi-002.html]
|
||||
fuzzy:
|
||||
if (os == "mac"): maxDifference=99-120;totalPixels=0-18
|
||||
if (os == "mac"): maxDifference=82-120;totalPixels=0-18
|
||||
if (os == "win"): maxDifference=13-143;totalPixels=0-19
|
||||
|
@ -4,3 +4,4 @@
|
||||
if win11_2009 and bits == 32: PASS
|
||||
if (os == "win") and (processor == "x86_64") and debug and not swgl: [FAIL, PASS]
|
||||
if (os == "win") and (processor == "x86_64") and not debug: FAIL
|
||||
if (os == "mac"): FAIL
|
||||
|
@ -0,0 +1,3 @@
|
||||
[hyphen-as-minus-sign.html]
|
||||
fuzzy:
|
||||
if (os == "mac"): maxDifference=0-1;totalPixels=0-47
|
Loading…
Reference in New Issue
Block a user