Gamma correction for distance field text.

Handles both non-LCD and LCD text. Uses a texture to look up the gamma correction values for a given text color or luminance.

BUG=skia:
R=reed@google.com, bungeman@google.com, robertphillips@google.com, bsalomon@google.com

Author: jvanverth@google.com

Review URL: https://codereview.chromium.org/258883002
diff --git a/include/core/SkPaint.h b/include/core/SkPaint.h
index 95fc0b8..f766ca1 100644
--- a/include/core/SkPaint.h
+++ b/include/core/SkPaint.h
@@ -1081,7 +1081,8 @@
     SkScalar measure_text(SkGlyphCache*, const char* text, size_t length,
                           int* count, SkRect* bounds) const;
 
-    SkGlyphCache* detachCache(const SkDeviceProperties* deviceProperties, const SkMatrix*) const;
+    SkGlyphCache* detachCache(const SkDeviceProperties* deviceProperties, const SkMatrix*,
+                              bool ignoreGamma) const;
 
     void descriptorProc(const SkDeviceProperties* deviceProperties, const SkMatrix* deviceMatrix,
                         void (*proc)(SkTypeface*, const SkDescriptor*, void*),
@@ -1131,6 +1132,7 @@
     }
 
     friend class SkAutoGlyphCache;
+    friend class SkAutoGlyphCacheNoGamma;
     friend class SkCanvas;
     friend class SkDraw;
     friend class SkGraphics; // So Term() can be called.
diff --git a/src/core/SkGlyphCache.h b/src/core/SkGlyphCache.h
index 8b404dc..d18b61a 100644
--- a/src/core/SkGlyphCache.h
+++ b/src/core/SkGlyphCache.h
@@ -244,23 +244,8 @@
     friend class SkGlyphCache_Globals;
 };
 
-class SkAutoGlyphCache {
+class SkAutoGlyphCacheBase {
 public:
-    SkAutoGlyphCache(SkGlyphCache* cache) : fCache(cache) {}
-    SkAutoGlyphCache(SkTypeface* typeface, const SkDescriptor* desc) {
-        fCache = SkGlyphCache::DetachCache(typeface, desc);
-    }
-    SkAutoGlyphCache(const SkPaint& paint,
-                     const SkDeviceProperties* deviceProperties,
-                     const SkMatrix* matrix) {
-        fCache = paint.detachCache(deviceProperties, matrix);
-    }
-    ~SkAutoGlyphCache() {
-        if (fCache) {
-            SkGlyphCache::AttachCache(fCache);
-        }
-    }
-
     SkGlyphCache* getCache() const { return fCache; }
 
     void release() {
@@ -270,11 +255,61 @@
         }
     }
 
-private:
+protected:
+    // Hide the constructors so we can't create one of these directly.
+    // Create SkAutoGlyphCache or SkAutoGlyphCacheNoCache instead.
+    SkAutoGlyphCacheBase(SkGlyphCache* cache) : fCache(cache) {}
+    SkAutoGlyphCacheBase(SkTypeface* typeface, const SkDescriptor* desc) {
+        fCache = SkGlyphCache::DetachCache(typeface, desc);
+    }
+    SkAutoGlyphCacheBase(const SkPaint& paint,
+                         const SkDeviceProperties* deviceProperties,
+                         const SkMatrix* matrix) {
+        fCache = NULL;
+    }
+    SkAutoGlyphCacheBase() {}
+
     SkGlyphCache*   fCache;
 
+private:
     static bool DetachProc(const SkGlyphCache*, void*);
 };
+
+class SkAutoGlyphCache : public SkAutoGlyphCacheBase {
+public:
+    SkAutoGlyphCache(SkGlyphCache* cache) : SkAutoGlyphCacheBase(cache) {}
+    SkAutoGlyphCache(SkTypeface* typeface, const SkDescriptor* desc) :
+        SkAutoGlyphCacheBase(typeface, desc) {}
+    SkAutoGlyphCache(const SkPaint& paint,
+                     const SkDeviceProperties* deviceProperties,
+                     const SkMatrix* matrix) {
+        fCache = paint.detachCache(deviceProperties, matrix, false);
+    }
+    SkAutoGlyphCache() : SkAutoGlyphCacheBase() {
+        if (fCache) {
+            SkGlyphCache::AttachCache(fCache);
+        }
+    }
+};
 #define SkAutoGlyphCache(...) SK_REQUIRE_LOCAL_VAR(SkAutoGlyphCache)
 
+class SkAutoGlyphCacheNoGamma : public SkAutoGlyphCacheBase {
+public:
+    SkAutoGlyphCacheNoGamma(SkGlyphCache* cache) : SkAutoGlyphCacheBase(cache) {}
+    SkAutoGlyphCacheNoGamma(SkTypeface* typeface, const SkDescriptor* desc) :
+        SkAutoGlyphCacheBase(typeface, desc) {}
+    SkAutoGlyphCacheNoGamma(const SkPaint& paint,
+                            const SkDeviceProperties* deviceProperties,
+                            const SkMatrix* matrix) {
+        fCache = paint.detachCache(deviceProperties, matrix, true);
+    }
+    SkAutoGlyphCacheNoGamma() : SkAutoGlyphCacheBase() {
+        if (fCache) {
+            SkGlyphCache::AttachCache(fCache);
+        }
+    }
+};
+#define SkAutoGlyphCacheNoGamma(...) SK_REQUIRE_LOCAL_VAR(SkAutoGlyphCacheNoGamma)
+
+
 #endif
diff --git a/src/core/SkMaskGamma.h b/src/core/SkMaskGamma.h
index 1f2b73c..08ed97f 100644
--- a/src/core/SkMaskGamma.h
+++ b/src/core/SkMaskGamma.h
@@ -136,6 +136,22 @@
      */
     PreBlend preBlend(SkColor color) const;
 
+    /**
+     * Get dimensions for the full table set, so it can be allocated as a block.
+     */
+    void getGammaTableDimensions(int* tableWidth, int* numTables) const {
+        *tableWidth = 256;
+        *numTables = (1 << MAX_LUM_BITS);
+    }
+
+    /**
+     * Provides direct access to the full table set, so it can be uploaded
+     * into a texture.
+     */
+    const uint8_t* getGammaTables() const {
+        return (const uint8_t*) fGammaTables;
+    }
+    
 private:
     static const int MAX_LUM_BITS =
           B_LUM_BITS > (R_LUM_BITS > G_LUM_BITS ? R_LUM_BITS : G_LUM_BITS)
diff --git a/src/core/SkPaint.cpp b/src/core/SkPaint.cpp
index 78d9470..b08b2c6 100644
--- a/src/core/SkPaint.cpp
+++ b/src/core/SkPaint.cpp
@@ -1814,10 +1814,8 @@
 
 /*
  *  ignoreGamma tells us that the caller just wants metrics that are unaffected
- *  by gamma correction, so we jam the luminance field to 0 (most common value
- *  for black text) in hopes that we get a cache hit easier. A better solution
- *  would be for the fontcache lookup to know to ignore the luminance field
- *  entirely, but not sure how to do that and keep it fast.
+ *  by gamma correction, so we set the rec to ignore preblend: i.e. gamma = 1,
+ *  contrast = 0, luminanceColor = transparent black.
  */
 void SkPaint::descriptorProc(const SkDeviceProperties* deviceProperties,
                              const SkMatrix* deviceMatrix,
@@ -1827,7 +1825,7 @@
 
     SkScalerContext::MakeRec(*this, deviceProperties, deviceMatrix, &rec);
     if (ignoreGamma) {
-        rec.setLuminanceColor(0);
+        rec.ignorePreBlend();
     }
 
     size_t          descSize = sizeof(rec);
@@ -1951,9 +1949,10 @@
 }
 
 SkGlyphCache* SkPaint::detachCache(const SkDeviceProperties* deviceProperties,
-                                   const SkMatrix* deviceMatrix) const {
+                                   const SkMatrix* deviceMatrix,
+                                   bool ignoreGamma) const {
     SkGlyphCache* cache;
-    this->descriptorProc(deviceProperties, deviceMatrix, DetachDescProc, &cache, false);
+    this->descriptorProc(deviceProperties, deviceMatrix, DetachDescProc, &cache, ignoreGamma);
     return cache;
 }
 
@@ -1969,6 +1968,33 @@
     return maskGamma.preBlend(rec.getLuminanceColor());
 }
 
+size_t SkScalerContext::GetGammaLUTSize(SkScalar contrast, SkScalar paintGamma,
+                                        SkScalar deviceGamma, int* width, int* height) {
+    SkAutoMutexAcquire ama(gMaskGammaCacheMutex);
+    const SkMaskGamma& maskGamma = cachedMaskGamma(contrast,
+                                                   paintGamma,
+                                                   deviceGamma);
+
+    maskGamma.getGammaTableDimensions(width, height);
+    size_t size = (*width)*(*height)*sizeof(uint8_t);
+
+    return size;
+}
+
+void SkScalerContext::GetGammaLUTData(SkScalar contrast, SkScalar paintGamma, SkScalar deviceGamma,
+                                      void* data) {
+    SkAutoMutexAcquire ama(gMaskGammaCacheMutex);
+    const SkMaskGamma& maskGamma = cachedMaskGamma(contrast,
+                                                   paintGamma,
+                                                   deviceGamma);
+    int width, height;
+    maskGamma.getGammaTableDimensions(&width, &height);
+    size_t size = width*height*sizeof(uint8_t);
+    const uint8_t* gammaTables = maskGamma.getGammaTables();
+    memcpy(data, gammaTables, size);
+}
+
+
 ///////////////////////////////////////////////////////////////////////////////
 
 #include "SkStream.h"
@@ -2557,7 +2583,7 @@
         fPaint.setPathEffect(NULL);
     }
 
-    fCache = fPaint.detachCache(NULL, NULL);
+    fCache = fPaint.detachCache(NULL, NULL, false);
 
     SkPaint::Style  style = SkPaint::kFill_Style;
     SkPathEffect*   pe = NULL;
diff --git a/src/core/SkScalerContext.h b/src/core/SkScalerContext.h
index f18d217..af83685 100644
--- a/src/core/SkScalerContext.h
+++ b/src/core/SkScalerContext.h
@@ -184,6 +184,17 @@
     void        getPath(const SkGlyph&, SkPath*);
     void        getFontMetrics(SkPaint::FontMetrics*);
 
+    /** Return the size in bytes of the associated gamma lookup table
+     */
+    static size_t GetGammaLUTSize(SkScalar contrast, SkScalar paintGamma, SkScalar deviceGamma,
+                                  int* width, int* height);
+
+    /** Get the associated gamma lookup table. The 'data' pointer must point to pre-allocated
+        memory, with size in bytes greater than or equal to the return value of getGammaLUTSize().
+     */
+    static void   GetGammaLUTData(SkScalar contrast, SkScalar paintGamma, SkScalar deviceGamma,
+                                  void* data);
+
 #ifdef SK_BUILD_FOR_ANDROID
     unsigned getBaseGlyphCount(SkUnichar charCode);
 
diff --git a/src/gpu/GrDistanceFieldTextContext.cpp b/src/gpu/GrDistanceFieldTextContext.cpp
index 2d2f9a1..a729939 100755
--- a/src/gpu/GrDistanceFieldTextContext.cpp
+++ b/src/gpu/GrDistanceFieldTextContext.cpp
@@ -7,10 +7,12 @@
 
 #include "GrDistanceFieldTextContext.h"
 #include "GrAtlas.h"
+#include "SkColorFilter.h"
 #include "GrDrawTarget.h"
 #include "GrDrawTargetCaps.h"
 #include "GrFontScaler.h"
 #include "SkGlyphCache.h"
+#include "GrGpu.h"
 #include "GrIndexBuffer.h"
 #include "GrTextStrike.h"
 #include "GrTextStrike_impl.h"
@@ -43,6 +45,7 @@
     fEnableDFRendering = enable;
 #endif
     fStrike = NULL;
+    fGammaTexture = NULL;
 
     fCurrTexture = NULL;
     fCurrVertex = 0;
@@ -53,6 +56,7 @@
 
 GrDistanceFieldTextContext::~GrDistanceFieldTextContext() {
     this->flushGlyphs();
+    SkSafeSetNull(fGammaTexture);
 }
 
 bool GrDistanceFieldTextContext::canDraw(const SkPaint& paint) {
@@ -107,14 +111,26 @@
         SkASSERT(SkIsAlign4(fCurrVertex));
         SkASSERT(fCurrTexture);
         GrTextureParams params(SkShader::kRepeat_TileMode, GrTextureParams::kBilerp_FilterMode);
+        GrTextureParams gammaParams(SkShader::kClamp_TileMode, GrTextureParams::kNone_FilterMode);
 
         // Effects could be stored with one of the cache objects (atlas?)
+        SkColor filteredColor;
+        SkColorFilter* colorFilter = fSkPaint.getColorFilter();
+        if (NULL != colorFilter) {
+            filteredColor = colorFilter->filterColor(fSkPaint.getColor());
+        } else {
+            filteredColor = fSkPaint.getColor();
+        }
         if (fUseLCDText) {
+            GrColor colorNoPreMul = skcolor_to_grcolor_nopremultiply(filteredColor);
             bool useBGR = SkDeviceProperties::Geometry::kBGR_Layout ==
                                                             fDeviceProperties.fGeometry.getLayout();
             drawState->addCoverageEffect(GrDistanceFieldLCDTextureEffect::Create(
                                                             fCurrTexture,
                                                             params,
+                                                            fGammaTexture,
+                                                            gammaParams,
+                                                            colorNoPreMul,
                                                             fContext->getMatrix().rectStaysRect() &&
                                                             fContext->getMatrix().isSimilarity(),
                                                             useBGR),
@@ -133,13 +149,24 @@
             // paintAlpha
             drawState->setColor(SkColorSetARGB(a, a, a, a));
             // paintColor
-            drawState->setBlendConstant(skcolor_to_grcolor_nopremultiply(fSkPaint.getColor()));
+            drawState->setBlendConstant(colorNoPreMul);
             drawState->setBlendFunc(kConstC_GrBlendCoeff, kISC_GrBlendCoeff);
         } else {
-            drawState->addCoverageEffect(GrDistanceFieldTextureEffect::Create(fCurrTexture, params,
+#ifdef SK_GAMMA_APPLY_TO_A8
+            U8CPU lum = SkColorSpaceLuminance::computeLuminance(fDeviceProperties.fGamma,
+                                                                filteredColor);
+            drawState->addCoverageEffect(GrDistanceFieldTextureEffect::Create(
+                                                              fCurrTexture, params,
+                                                              fGammaTexture, gammaParams,
+                                                              lum/255.f,
                                                               fContext->getMatrix().isSimilarity()),
                                          kGlyphCoordsAttributeIndex)->unref();
-
+#else
+            drawState->addCoverageEffect(GrDistanceFieldTextureEffect::Create(
+                                                              fCurrTexture, params,
+                                                              fContext->getMatrix().isSimilarity()),
+                                         kGlyphCoordsAttributeIndex)->unref();
+#endif
             // set back to normal in case we took LCD path previously.
             drawState->setBlendFunc(fPaint.getSrcBlendCoeff(), fPaint.getDstBlendCoeff());
             drawState->setColor(fPaint.getColor());
@@ -356,7 +383,9 @@
 
     fSkPaint.setLCDRenderText(false);
     fSkPaint.setAutohinted(false);
+    fSkPaint.setHinting(SkPaint::kNormal_Hinting);
     fSkPaint.setSubpixelText(true);
+
 }
 
 inline void GrDistanceFieldTextContext::finish() {
@@ -365,6 +394,46 @@
     GrTextContext::finish();
 }
 
+static void setup_gamma_texture(GrContext* context, const SkGlyphCache* cache,
+                                const SkDeviceProperties& deviceProperties,
+                                GrTexture** gammaTexture) {
+    if (NULL == *gammaTexture) {
+        int width, height;
+        size_t size;
+
+#ifdef SK_GAMMA_CONTRAST
+        SkScalar contrast = SK_GAMMA_CONTRAST;
+#else
+        SkScalar contrast = 0.5f;
+#endif
+        SkScalar paintGamma = deviceProperties.fGamma;
+        SkScalar deviceGamma = deviceProperties.fGamma;
+
+        size = SkScalerContext::GetGammaLUTSize(contrast, paintGamma, deviceGamma,
+                                                &width, &height);
+
+        SkAutoTArray<uint8_t> data((int)size);
+        SkScalerContext::GetGammaLUTData(contrast, paintGamma, deviceGamma, data.get());
+
+        // TODO: Update this to use the cache rather than directly creating a texture.
+        GrTextureDesc desc;
+        desc.fFlags = kDynamicUpdate_GrTextureFlagBit;
+        desc.fWidth = width;
+        desc.fHeight = height;
+        desc.fConfig = kAlpha_8_GrPixelConfig;
+
+        *gammaTexture = context->getGpu()->createTexture(desc, NULL, 0);
+        if (NULL == *gammaTexture) {
+            return;
+        }
+
+        context->writeTexturePixels(*gammaTexture,
+                                    0, 0, width, height,
+                                    (*gammaTexture)->config(), data.get(), 0,
+                                    GrContext::kDontFlush_PixelOpsFlag);
+    }
+}
+
 void GrDistanceFieldTextContext::drawText(const GrPaint& paint, const SkPaint& skPaint,
                                           const char text[], size_t byteLength,
                                           SkScalar x, SkScalar y) {
@@ -382,9 +451,11 @@
 
     SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
 
-    SkAutoGlyphCache    autoCache(fSkPaint, &fDeviceProperties, NULL);
-    SkGlyphCache*       cache = autoCache.getCache();
-    GrFontScaler*       fontScaler = GetGrFontScaler(cache);
+    SkAutoGlyphCacheNoGamma    autoCache(fSkPaint, &fDeviceProperties, NULL);
+    SkGlyphCache*              cache = autoCache.getCache();
+    GrFontScaler*              fontScaler = GetGrFontScaler(cache);
+
+    setup_gamma_texture(fContext, cache, fDeviceProperties, &fGammaTexture);
 
     // need to measure first
     // TODO - generate positions and pre-load cache as well?
@@ -455,9 +526,11 @@
 
     SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
 
-    SkAutoGlyphCache    autoCache(fSkPaint, &fDeviceProperties, NULL);
-    SkGlyphCache*       cache = autoCache.getCache();
-    GrFontScaler*       fontScaler = GetGrFontScaler(cache);
+    SkAutoGlyphCacheNoGamma    autoCache(fSkPaint, &fDeviceProperties, NULL);
+    SkGlyphCache*              cache = autoCache.getCache();
+    GrFontScaler*              fontScaler = GetGrFontScaler(cache);
+
+    setup_gamma_texture(fContext, cache, fDeviceProperties, &fGammaTexture);
 
     const char*        stop = text + byteLength;
 
diff --git a/src/gpu/GrDistanceFieldTextContext.h b/src/gpu/GrDistanceFieldTextContext.h
index 3dfffd1..3a602f0 100644
--- a/src/gpu/GrDistanceFieldTextContext.h
+++ b/src/gpu/GrDistanceFieldTextContext.h
@@ -34,6 +34,7 @@
     SkScalar                fTextRatio;
     bool                    fUseLCDText;
     bool                    fEnableDFRendering;
+    GrTexture*              fGammaTexture;
 
     void init(const GrPaint&, const SkPaint&);
     void drawPackedGlyph(GrGlyph::PackedID, SkFixed left, SkFixed top, GrFontScaler*);
diff --git a/src/gpu/effects/GrDistanceFieldTextureEffect.cpp b/src/gpu/effects/GrDistanceFieldTextureEffect.cpp
index 6196cce..29f0985 100755
--- a/src/gpu/effects/GrDistanceFieldTextureEffect.cpp
+++ b/src/gpu/effects/GrDistanceFieldTextureEffect.cpp
@@ -15,6 +15,19 @@
 
 #include "SkDistanceFieldGen.h"
 
+// To get optical sizes people don't complain about when we blit correctly,
+// we need to slightly bold each glyph. On the Mac, we need a larger bold value.
+#if defined(SK_BUILD_FOR_MAC)
+#define SK_DistanceFieldLCDFactor    "0.33"
+#define SK_DistanceFieldNonLCDFactor "0.25"
+#else
+#define SK_DistanceFieldLCDFactor    "0.05"
+#define SK_DistanceFieldNonLCDFactor "0.05"
+#endif
+
+// Assuming a radius of the diagonal of the fragment, hence a factor of sqrt(2)/2
+#define SK_DistanceFieldAAFactor     "0.7071"
+
 class GrGLDistanceFieldTextureEffect : public GrGLVertexEffect {
 public:
     GrGLDistanceFieldTextureEffect(const GrBackendEffectFactory& factory,
@@ -56,7 +69,8 @@
                                        kVec2f_GrSLType);
         builder->fsCodeAppend(";\n");
         builder->fsCodeAppend("\tfloat distance = "
-                     SK_DistanceFieldMultiplier "*(texColor.r - " SK_DistanceFieldThreshold ");\n");
+                          SK_DistanceFieldMultiplier "*(texColor.r - " SK_DistanceFieldThreshold ")"
+                          "+ " SK_DistanceFieldNonLCDFactor ";\n");
 
         // we adjust for the effect of the transformation on the distance by using
         // the length of the gradient of the texture coordinates. We use st coordinates
@@ -66,8 +80,7 @@
         builder->fsCodeAppend("\tfloat afwidth;\n");
         if (dfTexEffect.isSimilarity()) {
             // this gives us a smooth step across approximately one fragment
-            // (assuming a radius of the diagonal of the fragment, hence a factor of sqrt(2)/2)
-            builder->fsCodeAppend("\tafwidth = 0.7071*dFdx(st.x);\n");
+            builder->fsCodeAppend("\tafwidth = " SK_DistanceFieldAAFactor "*dFdx(st.x);\n");
         } else {
             builder->fsCodeAppend("\tvec2 Jdx = dFdx(st);\n");
             builder->fsCodeAppend("\tvec2 Jdy = dFdy(st);\n");
@@ -88,12 +101,25 @@
             builder->fsCodeAppend("\t                 uv_grad.x*Jdx.y + uv_grad.y*Jdy.y);\n");
 
             // this gives us a smooth step across approximately one fragment
-            // (assuming a radius of the diagonal of the fragment, hence a factor of sqrt(2)/2)
-            builder->fsCodeAppend("\tafwidth = 0.7071*length(grad);\n");
+            builder->fsCodeAppend("\tafwidth = " SK_DistanceFieldAAFactor "*length(grad);\n");
         }
-
         builder->fsCodeAppend("\tfloat val = smoothstep(-afwidth, afwidth, distance);\n");
 
+#ifdef SK_GAMMA_APPLY_TO_A8
+        // adjust based on gamma
+        const char* luminanceUniName = NULL;
+        // width, height, 1/(3*width)
+        fLuminanceUni = builder->addUniform(GrGLShaderBuilder::kFragment_Visibility,
+                                            kFloat_GrSLType, "Luminance",
+                                            &luminanceUniName);
+
+        builder->fsCodeAppendf("\tuv = vec2(val, %s);\n", luminanceUniName);
+        builder->fsCodeAppend("\tvec4 gammaColor = ");
+        builder->fsAppendTextureLookup(samplers[1], "uv", kVec2f_GrSLType);
+        builder->fsCodeAppend(";\n");
+        builder->fsCodeAppend("\tval = gammaColor.r;\n");
+#endif
+
         builder->fsCodeAppendf("\t%s = %s;\n", outputColor,
                                    (GrGLSLExpr4(inputColor) * GrGLSLExpr1("val")).c_str());
     }
@@ -110,6 +136,15 @@
                        SkIntToScalar(fTextureSize.width()),
                        SkIntToScalar(fTextureSize.height()));
         }
+#ifdef SK_GAMMA_APPLY_TO_A8
+        const GrDistanceFieldTextureEffect& dfTexEffect =
+                                              drawEffect.castEffect<GrDistanceFieldTextureEffect>();
+        float luminance = dfTexEffect.getLuminance();
+        if (luminance != fLuminance) {
+            uman.set1f(fLuminanceUni, luminance);
+            fLuminance = luminance;
+        }
+#endif
     }
 
     static inline EffectKey GenKey(const GrDrawEffect& drawEffect, const GrGLCaps&) {
@@ -122,6 +157,8 @@
 private:
     GrGLUniformManager::UniformHandle fTextureSizeUni;
     SkISize                           fTextureSize;
+    GrGLUniformManager::UniformHandle fLuminanceUni;
+    float                             fLuminance;
 
     typedef GrGLVertexEffect INHERITED;
 };
@@ -130,10 +167,22 @@
 
 GrDistanceFieldTextureEffect::GrDistanceFieldTextureEffect(GrTexture* texture,
                                                            const GrTextureParams& params,
+#ifdef SK_GAMMA_APPLY_TO_A8
+                                                           GrTexture* gamma,
+                                                           const GrTextureParams& gammaParams,
+                                                           float luminance,
+#endif
                                                            bool similarity)
     : fTextureAccess(texture, params)
+#ifdef SK_GAMMA_APPLY_TO_A8
+    , fGammaTextureAccess(gamma, gammaParams)
+    , fLuminance(luminance)
+#endif
     , fIsSimilarity(similarity) {
     this->addTextureAccess(&fTextureAccess);
+#ifdef SK_GAMMA_APPLY_TO_A8
+    this->addTextureAccess(&fGammaTextureAccess);
+#endif
     this->addVertexAttrib(kVec2f_GrSLType);
 }
 
@@ -166,6 +215,10 @@
                                                      GrTexture* textures[]) {
     int texIdx = random->nextBool() ? GrEffectUnitTest::kSkiaPMTextureIdx :
                                       GrEffectUnitTest::kAlphaTextureIdx;
+#ifdef SK_GAMMA_APPLY_TO_A8
+    int texIdx2 = random->nextBool() ? GrEffectUnitTest::kSkiaPMTextureIdx :
+                                       GrEffectUnitTest::kAlphaTextureIdx;
+#endif
     static const SkShader::TileMode kTileModes[] = {
         SkShader::kClamp_TileMode,
         SkShader::kRepeat_TileMode,
@@ -177,8 +230,16 @@
     };
     GrTextureParams params(tileModes, random->nextBool() ? GrTextureParams::kBilerp_FilterMode :
                                                            GrTextureParams::kNone_FilterMode);
+#ifdef SK_GAMMA_APPLY_TO_A8
+    GrTextureParams params2(tileModes, random->nextBool() ? GrTextureParams::kBilerp_FilterMode :
+                                                            GrTextureParams::kNone_FilterMode);
+#endif
 
     return GrDistanceFieldTextureEffect::Create(textures[texIdx], params,
+#ifdef SK_GAMMA_APPLY_TO_A8
+                                                textures[texIdx2], params2,
+                                                random->nextF(),
+#endif
                                                 random->nextBool());
 }
 
@@ -211,7 +272,7 @@
         fsCoordName = fsCoordNamePtr;
 
         const char* attrName0 =
-        builder->getEffectAttributeName(drawEffect.getVertexAttribIndices()[0])->c_str();
+                   builder->getEffectAttributeName(drawEffect.getVertexAttribIndices()[0])->c_str();
         builder->vsCodeAppendf("\t%s = %s;\n", vsCoordName, attrName0);
 
         const char* textureSizeUniName = NULL;
@@ -237,22 +298,24 @@
         builder->fsAppendTextureLookup(samplers[0], "uv", kVec2f_GrSLType);
         builder->fsCodeAppend(";\n");
         builder->fsCodeAppend("\tvec3 distance;\n");
-        builder->fsCodeAppend("\tdistance.y = "
-                     SK_DistanceFieldMultiplier "*(texColor.r - " SK_DistanceFieldThreshold ");\n");
+        builder->fsCodeAppend("\tdistance.y = texColor.r;\n");
         // red is distance to left offset
         builder->fsCodeAppend("\tvec2 uv_adjusted = uv - offset;\n");
         builder->fsCodeAppend("\ttexColor = ");
         builder->fsAppendTextureLookup(samplers[0], "uv_adjusted", kVec2f_GrSLType);
         builder->fsCodeAppend(";\n");
-        builder->fsCodeAppend("\tdistance.x = "
-                     SK_DistanceFieldMultiplier "*(texColor.r - " SK_DistanceFieldThreshold ");\n");
+        builder->fsCodeAppend("\tdistance.x = texColor.r;\n");
         // blue is distance to right offset
         builder->fsCodeAppend("\tuv_adjusted = uv + offset;\n");
         builder->fsCodeAppend("\ttexColor = ");
         builder->fsAppendTextureLookup(samplers[0], "uv_adjusted", kVec2f_GrSLType);
         builder->fsCodeAppend(";\n");
-        builder->fsCodeAppend("\tdistance.z = "
-                     SK_DistanceFieldMultiplier "*(texColor.r - " SK_DistanceFieldThreshold ");\n");
+        builder->fsCodeAppend("\tdistance.z = texColor.r;\n");
+
+        builder->fsCodeAppend("\tdistance = "
+            "vec3(" SK_DistanceFieldMultiplier ")*(distance - vec3(" SK_DistanceFieldThreshold"))"
+            "+ vec3(" SK_DistanceFieldLCDFactor ");\n");
+
         // we adjust for the effect of the transformation on the distance by using
         // the length of the gradient of the texture coordinates. We use st coordinates
         // to ensure we're mapping 1:1 from texel space to pixel space.
@@ -264,8 +327,7 @@
         builder->fsCodeAppend("\tfloat afwidth;\n");
         if (dfTexEffect.isUniformScale()) {
             // this gives us a smooth step across approximately one fragment
-            // (assuming a radius of the diagonal of the fragment, hence a factor of sqrt(2)/2)
-            builder->fsCodeAppend("\tafwidth = 0.7071*dx;\n");
+            builder->fsCodeAppend("\tafwidth = " SK_DistanceFieldAAFactor "*dx;\n");
         } else {
             builder->fsCodeAppend("\tvec2 uv_grad;\n");
             if (builder->ctxInfo().caps()->dropsTileOnZeroDivide()) {
@@ -283,12 +345,36 @@
             builder->fsCodeAppend("\t                 uv_grad.x*Jdx.y + uv_grad.y*Jdy.y);\n");
 
             // this gives us a smooth step across approximately one fragment
-            // (assuming a radius of the diagonal of the fragment, hence a factor of sqrt(2)/2)
-            builder->fsCodeAppend("\tafwidth = 0.7071*length(grad);\n");
+            builder->fsCodeAppend("\tafwidth = " SK_DistanceFieldAAFactor "*length(grad);\n");
         }
 
         builder->fsCodeAppend("\tvec4 val = vec4(smoothstep(vec3(-afwidth), vec3(afwidth), distance), 1.0);\n");
 
+        // adjust based on gamma
+        const char* textColorUniName = NULL;
+        // width, height, 1/(3*width)
+        fTextColorUni = builder->addUniform(GrGLShaderBuilder::kFragment_Visibility,
+                                            kVec3f_GrSLType, "TextColor",
+                                            &textColorUniName);
+
+        builder->fsCodeAppendf("\tuv = vec2(val.x, %s.x);\n", textColorUniName);
+        builder->fsCodeAppend("\tvec4 gammaColor = ");
+        builder->fsAppendTextureLookup(samplers[1], "uv", kVec2f_GrSLType);
+        builder->fsCodeAppend(";\n");
+        builder->fsCodeAppend("\tval.x = gammaColor.r;\n");
+
+        builder->fsCodeAppendf("\tuv = vec2(val.y, %s.y);\n", textColorUniName);
+        builder->fsCodeAppend("\tgammaColor = ");
+        builder->fsAppendTextureLookup(samplers[1], "uv", kVec2f_GrSLType);
+        builder->fsCodeAppend(";\n");
+        builder->fsCodeAppend("\tval.y = gammaColor.r;\n");
+
+        builder->fsCodeAppendf("\tuv = vec2(val.z, %s.z);\n", textColorUniName);
+        builder->fsCodeAppend("\tgammaColor = ");
+        builder->fsAppendTextureLookup(samplers[1], "uv", kVec2f_GrSLType);
+        builder->fsCodeAppend(";\n");
+        builder->fsCodeAppend("\tval.z = gammaColor.r;\n");
+
         builder->fsCodeAppendf("\t%s = %s;\n", outputColor,
                                (GrGLSLExpr4(inputColor) * GrGLSLExpr4("val")).c_str());
     }
@@ -296,12 +382,13 @@
     virtual void setData(const GrGLUniformManager& uman,
                          const GrDrawEffect& drawEffect) SK_OVERRIDE {
         SkASSERT(fTextureSizeUni.isValid());
+        SkASSERT(fTextColorUni.isValid());
 
+        const GrDistanceFieldLCDTextureEffect& dfTexEffect =
+                                    drawEffect.castEffect<GrDistanceFieldLCDTextureEffect>();
         GrTexture* texture = drawEffect.effect()->get()->texture(0);
         if (texture->width() != fTextureSize.width() ||
             texture->height() != fTextureSize.height()) {
-            const GrDistanceFieldLCDTextureEffect& dfTexEffect =
-                                           drawEffect.castEffect<GrDistanceFieldLCDTextureEffect>();
             fTextureSize = SkISize::Make(texture->width(), texture->height());
             float delta = 1.0f/(3.0f*texture->width());
             if (dfTexEffect.useBGR()) {
@@ -312,40 +399,55 @@
                        SkIntToScalar(fTextureSize.height()),
                        delta);
         }
+
+        GrColor textColor = dfTexEffect.getTextColor();
+        if (textColor != fTextColor) {
+            static const float ONE_OVER_255 = 1.f / 255.f;
+            uman.set3f(fTextColorUni,
+                       GrColorUnpackR(textColor) * ONE_OVER_255,
+                       GrColorUnpackG(textColor) * ONE_OVER_255,
+                       GrColorUnpackB(textColor) * ONE_OVER_255);
+            fTextColor = textColor;
+        }
     }
 
     static inline EffectKey GenKey(const GrDrawEffect& drawEffect, const GrGLCaps&) {
         const GrDistanceFieldLCDTextureEffect& dfTexEffect =
                                            drawEffect.castEffect<GrDistanceFieldLCDTextureEffect>();
 
-        int uniformScale = dfTexEffect.isUniformScale() ? 0x01 : 0x00;
-        int useBGR = dfTexEffect.useBGR() ? 0x10 : 0x00;
-        return uniformScale | useBGR;
+        return dfTexEffect.isUniformScale() ? 0x01 : 0x00;;
     }
 
 private:
     GrGLUniformManager::UniformHandle fTextureSizeUni;
     SkISize                           fTextureSize;
+    GrGLUniformManager::UniformHandle fTextColorUni;
+    SkColor                           fTextColor;
 
     typedef GrGLVertexEffect INHERITED;
 };
 
 ///////////////////////////////////////////////////////////////////////////////
 
-GrDistanceFieldLCDTextureEffect::GrDistanceFieldLCDTextureEffect(GrTexture* texture,
-                                                                 const GrTextureParams& params,
-                                                                 bool uniformScale,
-                                                                 bool useBGR)
+GrDistanceFieldLCDTextureEffect::GrDistanceFieldLCDTextureEffect(
+                                                  GrTexture* texture, const GrTextureParams& params,
+                                                  GrTexture* gamma, const GrTextureParams& gParams,
+                                                  SkColor textColor,
+                                                  bool uniformScale, bool useBGR)
     : fTextureAccess(texture, params)
+    , fGammaTextureAccess(gamma, gParams)
+    , fTextColor(textColor)
     , fUniformScale(uniformScale)
     , fUseBGR(useBGR) {
     this->addTextureAccess(&fTextureAccess);
+    this->addTextureAccess(&fGammaTextureAccess);
     this->addVertexAttrib(kVec2f_GrSLType);
 }
 
 bool GrDistanceFieldLCDTextureEffect::onIsEqual(const GrEffect& other) const {
-    const GrDistanceFieldLCDTextureEffect& cte = CastEffect<GrDistanceFieldLCDTextureEffect>(other);
-    return fTextureAccess == cte.fTextureAccess;
+    const GrDistanceFieldLCDTextureEffect& cte = 
+                                            CastEffect<GrDistanceFieldLCDTextureEffect>(other);
+    return (fTextureAccess == cte.fTextureAccess && fGammaTextureAccess == cte.fGammaTextureAccess);
 }
 
 void GrDistanceFieldLCDTextureEffect::getConstantColorComponents(GrColor* color,
@@ -371,7 +473,9 @@
                                                          const GrDrawTargetCaps&,
                                                          GrTexture* textures[]) {
     int texIdx = random->nextBool() ? GrEffectUnitTest::kSkiaPMTextureIdx :
-    GrEffectUnitTest::kAlphaTextureIdx;
+                                      GrEffectUnitTest::kAlphaTextureIdx;
+    int texIdx2 = random->nextBool() ? GrEffectUnitTest::kSkiaPMTextureIdx :
+                                       GrEffectUnitTest::kAlphaTextureIdx;
     static const SkShader::TileMode kTileModes[] = {
         SkShader::kClamp_TileMode,
         SkShader::kRepeat_TileMode,
@@ -383,7 +487,14 @@
     };
     GrTextureParams params(tileModes, random->nextBool() ? GrTextureParams::kBilerp_FilterMode :
                            GrTextureParams::kNone_FilterMode);
-
+    GrTextureParams params2(tileModes, random->nextBool() ? GrTextureParams::kBilerp_FilterMode :
+                           GrTextureParams::kNone_FilterMode);
+    GrColor textColor = GrColorPackRGBA(random->nextULessThan(256),
+                                        random->nextULessThan(256),
+                                        random->nextULessThan(256),
+                                        random->nextULessThan(256));
     return GrDistanceFieldLCDTextureEffect::Create(textures[texIdx], params,
+                                                   textures[texIdx2], params2,
+                                                   textColor,
                                                    random->nextBool(), random->nextBool());
 }
diff --git a/src/gpu/effects/GrDistanceFieldTextureEffect.h b/src/gpu/effects/GrDistanceFieldTextureEffect.h
index 937908f..692290c 100644
--- a/src/gpu/effects/GrDistanceFieldTextureEffect.h
+++ b/src/gpu/effects/GrDistanceFieldTextureEffect.h
@@ -18,12 +18,23 @@
  * The output color of this effect is a modulation of the input color and a sample from a
  * distance field texture (using a smoothed step function near 0.5).
  * It allows explicit specification of the filtering and wrap modes (GrTextureParams). The input
- * coords are a custom attribute.
+ * coords are a custom attribute. Gamma correction is handled via a texture LUT.
  */
 class GrDistanceFieldTextureEffect : public GrVertexEffect {
 public:
-    static GrEffectRef* Create(GrTexture* tex, const GrTextureParams& params, bool similarity) {
-        AutoEffectUnref effect(SkNEW_ARGS(GrDistanceFieldTextureEffect, (tex, params, similarity)));
+#ifdef SK_GAMMA_APPLY_TO_A8
+    static GrEffectRef* Create(GrTexture* tex, const GrTextureParams& params,
+                               GrTexture* gamma, const GrTextureParams& gammaParams, float lum,
+                               bool similarity) {
+        AutoEffectUnref effect(SkNEW_ARGS(GrDistanceFieldTextureEffect, (tex, params,
+                                                                         gamma, gammaParams, lum,
+                                                                         similarity)));
+#else
+    static GrEffectRef* Create(GrTexture* tex, const GrTextureParams& params,
+                               bool similarity) {
+        AutoEffectUnref effect(SkNEW_ARGS(GrDistanceFieldTextureEffect, (tex, params,
+                                                                         similarity)));
+#endif
         return CreateEffectRef(effect);
     }
 
@@ -32,6 +43,9 @@
     static const char* Name() { return "DistanceFieldTexture"; }
 
     virtual void getConstantColorComponents(GrColor* color, uint32_t* validFlags) const SK_OVERRIDE;
+#ifdef SK_GAMMA_APPLY_TO_A8
+    float getLuminance() const { return fLuminance; }
+#endif
     bool isSimilarity() const { return fIsSimilarity; }
 
     typedef GrGLDistanceFieldTextureEffect GLEffect;
@@ -40,11 +54,18 @@
 
 private:
     GrDistanceFieldTextureEffect(GrTexture* texture, const GrTextureParams& params,
+#ifdef SK_GAMMA_APPLY_TO_A8
+                                 GrTexture* gamma, const GrTextureParams& gammaParams, float lum,
+#endif
                                  bool uniformScale);
 
     virtual bool onIsEqual(const GrEffect& other) const SK_OVERRIDE;
 
     GrTextureAccess fTextureAccess;
+#ifdef SK_GAMMA_APPLY_TO_A8
+    GrTextureAccess fGammaTextureAccess;
+    float           fLuminance;
+#endif
     bool            fIsSimilarity;
 
     GR_DECLARE_EFFECT_TEST;
@@ -56,14 +77,17 @@
  * The output color of this effect is a modulation of the input color and samples from a
  * distance field texture (using a smoothed step function near 0.5), adjusted for LCD displays.
  * It allows explicit specification of the filtering and wrap modes (GrTextureParams). The input
- * coords are a custom attribute.
+ * coords are a custom attribute. Gamma correction is handled via a texture LUT.
  */
 class GrDistanceFieldLCDTextureEffect : public GrVertexEffect {
 public:
     static GrEffectRef* Create(GrTexture* tex, const GrTextureParams& params,
+                               GrTexture* gamma, const GrTextureParams& gammaParams, 
+                               SkColor textColor,
                                bool uniformScale, bool useBGR) {
         AutoEffectUnref effect(SkNEW_ARGS(GrDistanceFieldLCDTextureEffect,
-                                          (tex, params, uniformScale, useBGR)));
+                                          (tex, params, gamma, gammaParams, textColor, uniformScale,
+                                           useBGR)));
         return CreateEffectRef(effect);
     }
 
@@ -72,6 +96,7 @@
     static const char* Name() { return "DistanceFieldLCDTexture"; }
 
     virtual void getConstantColorComponents(GrColor* color, uint32_t* validFlags) const SK_OVERRIDE;
+    GrColor getTextColor() const { return fTextColor; }
     bool isUniformScale() const { return fUniformScale; }
     bool useBGR() const { return fUseBGR; }
 
@@ -81,11 +106,15 @@
 
 private:
     GrDistanceFieldLCDTextureEffect(GrTexture* texture, const GrTextureParams& params,
+                                    GrTexture* gamma, const GrTextureParams& gammaParams,
+                                    SkColor textColor,
                                     bool uniformScale, bool useBGR);
 
     virtual bool onIsEqual(const GrEffect& other) const SK_OVERRIDE;
 
     GrTextureAccess fTextureAccess;
+    GrTextureAccess fGammaTextureAccess;
+    GrColor         fTextColor;
     bool            fUniformScale;
     bool            fUseBGR;
 
@@ -94,5 +123,4 @@
     typedef GrVertexEffect INHERITED;
 };
 
-
 #endif