Bug 1531867 - support light and dark subpixel AA masks in Skia on mac. r=mstange a=lizzard
authorLee Salzman <lsalzman@mozilla.com>
Fri, 01 Mar 2019 14:01:58 -0500
changeset 516273 9b08cd8e30587526c6d9cda23b6b4902ac0ae9d2
parent 516272 ca92748672f1763548d554c1b5711d25318c33dc
child 516274 d65b3702be545d8323a54a46f8a236c83e0601dd
push id1953
push userffxbld-merge
push dateMon, 11 Mar 2019 12:10:20 +0000
treeherdermozilla-release@9c35dcbaa899 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmstange, lizzard
bugs1531867
milestone66.0
Bug 1531867 - support light and dark subpixel AA masks in Skia on mac. r=mstange a=lizzard 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.