Home | History | Annotate | Download | only in text
      1 /*
      2  * Copyright 2015 Google Inc.
      3  *
      4  * Use of this source code is governed by a BSD-style license that can be
      5  * found in the LICENSE file.
      6  */
      7 
      8 #include "GrDistanceFieldAdjustTable.h"
      9 
     10 #include "SkScalerContext.h"
     11 
     12 SkDEBUGCODE(static const int kExpectedDistanceAdjustTableSize = 8;)
     13 
     14 SkScalar* build_distance_adjust_table(SkScalar paintGamma, SkScalar deviceGamma) {
     15     // This is used for an approximation of the mask gamma hack, used by raster and bitmap
     16     // text. The mask gamma hack is based off of guessing what the blend color is going to
     17     // be, and adjusting the mask so that when run through the linear blend will
     18     // produce the value closest to the desired result. However, in practice this means
     19     // that the 'adjusted' mask is just increasing or decreasing the coverage of
     20     // the mask depending on what it is thought it will blit against. For black (on
     21     // assumed white) this means that coverages are decreased (on a curve). For white (on
     22     // assumed black) this means that coverages are increased (on a a curve). At
     23     // middle (perceptual) gray (which could be blit against anything) the coverages
     24     // remain the same.
     25     //
     26     // The idea here is that instead of determining the initial (real) coverage and
     27     // then adjusting that coverage, we determine an adjusted coverage directly by
     28     // essentially manipulating the geometry (in this case, the distance to the glyph
     29     // edge). So for black (on assumed white) this thins a bit; for white (on
     30     // assumed black) this fake bolds the geometry a bit.
     31     //
     32     // The distance adjustment is calculated by determining the actual coverage value which
     33     // when fed into in the mask gamma table gives us an 'adjusted coverage' value of 0.5. This
     34     // actual coverage value (assuming it's between 0 and 1) corresponds to a distance from the
     35     // actual edge. So by subtracting this distance adjustment and computing without the
     36     // the coverage adjustment we should get 0.5 coverage at the same point.
     37     //
     38     // This has several implications:
     39     //     For non-gray lcd smoothed text, each subpixel essentially is using a
     40     //     slightly different geometry.
     41     //
     42     //     For black (on assumed white) this may not cover some pixels which were
     43     //     previously covered; however those pixels would have been only slightly
     44     //     covered and that slight coverage would have been decreased anyway. Also, some pixels
     45     //     which were previously fully covered may no longer be fully covered.
     46     //
     47     //     For white (on assumed black) this may cover some pixels which weren't
     48     //     previously covered at all.
     49 
     50     int width, height;
     51     size_t size;
     52 
     53 #ifdef SK_GAMMA_CONTRAST
     54     SkScalar contrast = SK_GAMMA_CONTRAST;
     55 #else
     56     SkScalar contrast = 0.5f;
     57 #endif
     58 
     59     size = SkScalerContext::GetGammaLUTSize(contrast, paintGamma, deviceGamma,
     60         &width, &height);
     61 
     62     SkASSERT(kExpectedDistanceAdjustTableSize == height);
     63     SkScalar* table = new SkScalar[height];
     64 
     65     SkAutoTArray<uint8_t> data((int)size);
     66     if (!SkScalerContext::GetGammaLUTData(contrast, paintGamma, deviceGamma, data.get())) {
     67         // if no valid data is available simply do no adjustment
     68         for (int row = 0; row < height; ++row) {
     69             table[row] = 0;
     70         }
     71         return table;
     72     }
     73 
     74     // find the inverse points where we cross 0.5
     75     // binsearch might be better, but we only need to do this once on creation
     76     for (int row = 0; row < height; ++row) {
     77         uint8_t* rowPtr = data.get() + row*width;
     78         for (int col = 0; col < width - 1; ++col) {
     79             if (rowPtr[col] <= 127 && rowPtr[col + 1] >= 128) {
     80                 // compute point where a mask value will give us a result of 0.5
     81                 float interp = (127.5f - rowPtr[col]) / (rowPtr[col + 1] - rowPtr[col]);
     82                 float borderAlpha = (col + interp) / 255.f;
     83 
     84                 // compute t value for that alpha
     85                 // this is an approximate inverse for smoothstep()
     86                 float t = borderAlpha*(borderAlpha*(4.0f*borderAlpha - 6.0f) + 5.0f) / 3.0f;
     87 
     88                 // compute distance which gives us that t value
     89                 const float kDistanceFieldAAFactor = 0.65f; // should match SK_DistanceFieldAAFactor
     90                 float d = 2.0f*kDistanceFieldAAFactor*t - kDistanceFieldAAFactor;
     91 
     92                 table[row] = d;
     93                 break;
     94             }
     95         }
     96     }
     97 
     98     return table;
     99 }
    100 
    101 void GrDistanceFieldAdjustTable::buildDistanceAdjustTables() {
    102     fTable = build_distance_adjust_table(SK_GAMMA_EXPONENT, SK_GAMMA_EXPONENT);
    103     fGammaCorrectTable = build_distance_adjust_table(SK_Scalar1, SK_Scalar1);
    104 }
    105