Bug 1531867 - support light and dark subpixel AA masks in Skia on mac. r=mstange
authorLee Salzman <lsalzman@mozilla.com>
Fri, 01 Mar 2019 14:01:58 -0500
changeset 520011 571c5aa0458018f62d1f0832fc0e92fa3f23fda9
parent 520010 2e63fa41257b8950f5726fd88936ba3f5e2d8441
child 520012 812a2e26c05ea230f0104d2a30a19ff5d8ba7529
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmstange
bugs1531867
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1531867 - support light and dark subpixel AA masks in Skia on mac. r=mstange Differential Revision: https://phabricator.services.mozilla.com/D21763
gfx/skia/skia/src/core/SkScalerContext.h
gfx/skia/skia/src/ports/SkFontHost_mac.cpp
--- a/gfx/skia/skia/src/core/SkScalerContext.h
+++ b/gfx/skia/skia/src/core/SkScalerContext.h
@@ -21,16 +21,17 @@
 #include "SkWriteBuffer.h"
 
 class SkAutoDescriptor;
 class SkDescriptor;
 class SkMaskFilter;
 class SkPathEffect;
 class SkScalerContext;
 class SkScalerContext_DW;
+class SkTypeface_Mac;
 
 enum SkScalerContextFlags : uint32_t {
     kNone                      = 0,
     kFakeGamma                 = 1 << 0,
     kBoostContrast             = 1 << 1,
     kFakeGammaAndBoostContrast = kFakeGamma | kBoostContrast,
 };
 
@@ -202,16 +203,17 @@ public:
     SkMask::Format getFormat() const {
         return static_cast<SkMask::Format>(fMaskFormat);
     }
 
 private:
     // TODO: get rid of these bad friends.
     friend class SkScalerContext;
     friend class SkScalerContext_DW;
+    friend class SkTypeface_Mac;
 
     SkColor getLuminanceColor() const {
         return fLumBits;
     }
 
 
     void setLuminanceColor(SkColor c) {
         fLumBits = c;
@@ -246,16 +248,18 @@ public:
         // Pixel geometry information.
         // only meaningful if fMaskFormat is kLCD16
         kLCD_Vertical_Flag        = 0x0200,    // else Horizontal
         kLCD_BGROrder_Flag        = 0x0400,    // else RGB order
 
         // Generate A8 from LCD source (for GDI and CoreGraphics).
         // only meaningful if fMaskFormat is kA8
         kGenA8FromLCD_Flag        = 0x0800, // could be 0x200 (bit meaning dependent on fMaskFormat)
+
+        kLightOnDark_Flag         = 0x8000, // Moz + Mac only, used to distinguish different mask dilations
     };
 
     // computed values
     enum {
         kHinting_Mask   = kHintingBit1_Flag | kHintingBit2_Flag,
     };
 
     SkScalerContext(sk_sp<SkTypeface>, const SkScalerContextEffects&, const SkDescriptor*);
--- a/gfx/skia/skia/src/ports/SkFontHost_mac.cpp
+++ b/gfx/skia/skia/src/ports/SkFontHost_mac.cpp
@@ -451,17 +451,18 @@ public:
         , fCG(nullptr)
         , fDoAA(false)
         , fDoLCD(false)
     {
         fSize.set(0, 0);
     }
 
     CGRGBPixel* getCG(const SkScalerContext_Mac& context, const SkGlyph& glyph,
-                      CGGlyph glyphID, size_t* rowBytesPtr, bool generateA8FromLCD);
+                      CGGlyph glyphID, size_t* rowBytesPtr, bool generateA8FromLCD,
+                      bool lightOnDark);
 
 private:
     enum {
         kSize = 32 * 32 * sizeof(CGRGBPixel)
     };
     SkAutoSMalloc<kSize> fImageStorage;
     SkUniqueCFRef<CGColorSpaceRef> fRGBSpace;
 
@@ -1077,17 +1078,17 @@ SkScalerContext_Mac::SkScalerContext_Mac
     // Some properties, like 'trak', are based on the text size (before applying the matrix).
     CGFloat textSize = ScalarToCG(scale.y());
     fCTFont = ctfont_create_exact_copy(ctFont, textSize, nullptr);
     fCGFont.reset(CTFontCopyGraphicsFont(fCTFont.get(), nullptr));
 }
 
 CGRGBPixel* Offscreen::getCG(const SkScalerContext_Mac& context, const SkGlyph& glyph,
                              CGGlyph glyphID, size_t* rowBytesPtr,
-                             bool generateA8FromLCD) {
+                             bool generateA8FromLCD, bool lightOnDark) {
     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))
         //and subpixel antialiased text is always g=2.0.
         fRGBSpace.reset(CGColorSpaceCreateDeviceRGB());
     }
 
     // default to kBW_Format
@@ -1139,17 +1140,18 @@ CGRGBPixel* Offscreen::getCG(const SkSca
         // if there is a non-integral translation from the horizontal origin to the vertical origin,
         // then CG cannot draw the glyph in the correct location without subpixel positioning.
         CGContextSetAllowsFontSubpixelPositioning(fCG.get(), true);
         CGContextSetShouldSubpixelPositionFonts(fCG.get(), true);
 
         CGContextSetTextDrawingMode(fCG.get(), kCGTextFill);
 
         // Draw black on white to create mask. (Special path exists to speed this up in CG.)
-        CGContextSetGrayFillColor(fCG.get(), 0.0f, 1.0f);
+        // If light-on-dark is requested, draw white on black.
+        CGContextSetGrayFillColor(fCG.get(), lightOnDark ? 1.0f : 0.0f, 1.0f);
 
         // force our checks below to happen
         fDoAA = !doAA;
         fDoLCD = !doLCD;
 
         CGContextSetTextMatrix(fCG.get(), context.fTransform);
     }
 
@@ -1162,17 +1164,18 @@ CGRGBPixel* Offscreen::getCG(const SkSca
         fDoLCD = doLCD;
     }
 
     CGRGBPixel* image = (CGRGBPixel*)fImageStorage.get();
     // skip rows based on the glyph's height
     image += (fSize.fHeight - glyph.fHeight) * fSize.fWidth;
 
     // Erase to white (or transparent black if it's a color glyph, to not composite against white).
-    uint32_t bgColor = (SkMask::kARGB32_Format != glyph.fMaskFormat) ? 0xFFFFFFFF : 0x00000000;
+    // For light-on-dark, instead erase to black.
+    uint32_t bgColor = (SkMask::kARGB32_Format != glyph.fMaskFormat) ? (lightOnDark ? 0xFF000000 : 0xFFFFFFFF) : 0x00000000;
     sk_memset_rect32(image, bgColor, glyph.fWidth, glyph.fHeight, rowBytes);
 
     float subX = 0;
     float subY = 0;
     if (context.fDoSubPosition) {
         subX = SkFixedToFloat(glyph.getSubXFixed());
         subY = SkFixedToFloat(glyph.getSubYFixed());
     }
@@ -1430,20 +1433,21 @@ static SkPMColor cgpixels_to_pmcolor(CGR
     return SkPackARGB32(a, r, g, b);
 }
 
 void SkScalerContext_Mac::generateImage(const SkGlyph& glyph) {
     CGGlyph cgGlyph = SkTo<CGGlyph>(glyph.getGlyphID());
 
     // FIXME: lcd smoothed un-hinted rasterization unsupported.
     bool requestSmooth = fRec.getHinting() != SkPaint::kNo_Hinting;
+    bool lightOnDark = (fRec.fFlags & SkScalerContext::kLightOnDark_Flag) != 0;
 
     // Draw the glyph
     size_t cgRowBytes;
-    CGRGBPixel* cgPixels = fOffscreen.getCG(*this, glyph, cgGlyph, &cgRowBytes, requestSmooth);
+    CGRGBPixel* cgPixels = fOffscreen.getCG(*this, glyph, cgGlyph, &cgRowBytes, requestSmooth, lightOnDark);
     if (cgPixels == nullptr) {
         return;
     }
 
     // Fix the glyph
     if ((glyph.fMaskFormat == SkMask::kLCD16_Format) ||
         (glyph.fMaskFormat == SkMask::kA8_Format
          && requestSmooth
@@ -1454,20 +1458,26 @@ void SkScalerContext_Mac::generateImage(
         //Note that the following cannot really be integrated into the
         //pre-blend, since we may not be applying the pre-blend; when we aren't
         //applying the pre-blend it means that a filter wants linear anyway.
         //Other code may also be applying the pre-blend, so we'd need another
         //one with this and one without.
         CGRGBPixel* addr = cgPixels;
         for (int y = 0; y < glyph.fHeight; ++y) {
             for (int x = 0; x < glyph.fWidth; ++x) {
-                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];
+                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;
             }
             addr = SkTAddOffset<CGRGBPixel>(addr, cgRowBytes);
         }
     }
 
     // Convert glyph to mask
     switch (glyph.fMaskFormat) {
         case SkMask::kLCD16_Format: {
@@ -2322,16 +2332,29 @@ size_t SkTypeface_Mac::onGetTableData(Sk
 }
 
 SkScalerContext* SkTypeface_Mac::onCreateScalerContext(const SkScalerContextEffects& effects,
                                                        const SkDescriptor* desc) const {
     return new SkScalerContext_Mac(sk_ref_sp(const_cast<SkTypeface_Mac*>(this)), effects, desc);
 }
 
 void SkTypeface_Mac::onFilterRec(SkScalerContextRec* rec) const {
+    if (rec->fMaskFormat == SkMask::kLCD16_Format ||
+        rec->fFlags & SkScalerContext::kGenA8FromLCD_Flag) {
+        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;
+        }
+    }
+
     if (rec->fFlags & SkScalerContext::kLCD_BGROrder_Flag ||
         rec->fFlags & SkScalerContext::kLCD_Vertical_Flag)
     {
         rec->fMaskFormat = SkMask::kA8_Format;
         // Render the glyphs as close as possible to what was requested.
         // The above turns off subpixel rendering, but the user requested it.
         // Normal hinting will cause the A8 masks to be generated from CoreGraphics subpixel masks.
         // See comments below for more details.