OLD | NEW |
1 /* | 1 /* |
2 * Copyright 2013 Google Inc. | 2 * Copyright 2013 Google Inc. |
3 * | 3 * |
4 * Use of this source code is governed by a BSD-style license that can be | 4 * Use of this source code is governed by a BSD-style license that can be |
5 * found in the LICENSE file. | 5 * found in the LICENSE file. |
6 */ | 6 */ |
7 | 7 |
8 #include "GrDistanceFieldTextContext.h" | 8 #include "GrDistanceFieldTextContext.h" |
9 #include "GrAtlas.h" | 9 #include "GrAtlas.h" |
10 #include "GrBitmapTextContext.h" | 10 #include "GrBitmapTextContext.h" |
(...skipping 24 matching lines...) Expand all Loading... |
35 static const int kMinDFFontSize = 18; | 35 static const int kMinDFFontSize = 18; |
36 static const int kSmallDFFontSize = 32; | 36 static const int kSmallDFFontSize = 32; |
37 static const int kSmallDFFontLimit = 32; | 37 static const int kSmallDFFontLimit = 32; |
38 static const int kMediumDFFontSize = 72; | 38 static const int kMediumDFFontSize = 72; |
39 static const int kMediumDFFontLimit = 72; | 39 static const int kMediumDFFontLimit = 72; |
40 static const int kLargeDFFontSize = 162; | 40 static const int kLargeDFFontSize = 162; |
41 | 41 |
42 static const int kVerticesPerGlyph = 4; | 42 static const int kVerticesPerGlyph = 4; |
43 static const int kIndicesPerGlyph = 6; | 43 static const int kIndicesPerGlyph = 6; |
44 | 44 |
| 45 #ifdef SK_DEBUG |
| 46 static const int kExpectedDistanceAdjustTableSize = 8; |
| 47 #endif |
| 48 static const int kDistanceAdjustLumShift = 5; |
| 49 |
45 GrDistanceFieldTextContext::GrDistanceFieldTextContext(GrContext* context, | 50 GrDistanceFieldTextContext::GrDistanceFieldTextContext(GrContext* context, |
46 SkGpuDevice* gpuDevice, | 51 SkGpuDevice* gpuDevice, |
47 const SkDeviceProperties&
properties, | 52 const SkDeviceProperties&
properties, |
48 bool enable) | 53 bool enable) |
49 : GrTextContext(context, gpuDevice, properties) { | 54 : GrTextContext(context, gpuDevice, properties) { |
50 #if SK_FORCE_DISTANCE_FIELD_TEXT | 55 #if SK_FORCE_DISTANCE_FIELD_TEXT |
51 fEnableDFRendering = true; | 56 fEnableDFRendering = true; |
52 #else | 57 #else |
53 fEnableDFRendering = enable; | 58 fEnableDFRendering = enable; |
54 #endif | 59 #endif |
55 fStrike = NULL; | 60 fStrike = NULL; |
56 fGammaTexture = NULL; | 61 fDistanceAdjustTable = NULL; |
57 | 62 |
58 fEffectTextureUniqueID = SK_InvalidUniqueID; | 63 fEffectTextureUniqueID = SK_InvalidUniqueID; |
59 fEffectColor = GrColor_ILLEGAL; | 64 fEffectColor = GrColor_ILLEGAL; |
60 fEffectFlags = kInvalid_DistanceFieldEffectFlag; | 65 fEffectFlags = kInvalid_DistanceFieldEffectFlag; |
61 | 66 |
62 fVertices = NULL; | 67 fVertices = NULL; |
63 fCurrVertex = 0; | 68 fCurrVertex = 0; |
64 fAllocVertexCount = 0; | 69 fAllocVertexCount = 0; |
65 fTotalVertexCount = 0; | 70 fTotalVertexCount = 0; |
66 fCurrTexture = NULL; | 71 fCurrTexture = NULL; |
67 | 72 |
68 fVertexBounds.setLargestInverted(); | 73 fVertexBounds.setLargestInverted(); |
69 } | 74 } |
70 | 75 |
71 GrDistanceFieldTextContext* GrDistanceFieldTextContext::Create(GrContext* contex
t, | 76 GrDistanceFieldTextContext* GrDistanceFieldTextContext::Create(GrContext* contex
t, |
72 SkGpuDevice* gpuD
evice, | 77 SkGpuDevice* gpuD
evice, |
73 const SkDevicePro
perties& props, | 78 const SkDevicePro
perties& props, |
74 bool enable) { | 79 bool enable) { |
75 GrDistanceFieldTextContext* textContext = SkNEW_ARGS(GrDistanceFieldTextCont
ext, | 80 GrDistanceFieldTextContext* textContext = SkNEW_ARGS(GrDistanceFieldTextCont
ext, |
76 (context, gpuDevice, pr
ops, enable)); | 81 (context, gpuDevice, pr
ops, enable)); |
| 82 textContext->buildDistanceAdjustTable(); |
77 #ifdef USE_BITMAP_TEXTBLOBS | 83 #ifdef USE_BITMAP_TEXTBLOBS |
78 textContext->fFallbackTextContext = GrBitmapTextContextB::Create(context, gp
uDevice, props); | 84 textContext->fFallbackTextContext = GrBitmapTextContextB::Create(context, gp
uDevice, props); |
79 #else | 85 #else |
80 textContext->fFallbackTextContext = GrBitmapTextContext::Create(context, gpu
Device, props); | 86 textContext->fFallbackTextContext = GrBitmapTextContext::Create(context, gpu
Device, props); |
81 #endif | 87 #endif |
82 | 88 |
83 return textContext; | 89 return textContext; |
84 } | 90 } |
85 | 91 |
| 92 void GrDistanceFieldTextContext::buildDistanceAdjustTable() { |
| 93 |
| 94 // This is used for an approximation of the mask gamma hack, used by raster
and bitmap |
| 95 // text. The mask gamma hack is based off of guessing what the blend color i
s going to |
| 96 // be, and adjusting the mask so that when run through the linear blend will |
| 97 // produce the value closest to the desired result. However, in practice thi
s means |
| 98 // that the 'adjusted' mask is just increasing or decreasing the coverage of |
| 99 // the mask depending on what it is thought it will blit against. For black
(on |
| 100 // assumed white) this means that coverages are decreased (on a curve). For
white (on |
| 101 // assumed black) this means that coverages are increased (on a a curve). At |
| 102 // middle (perceptual) gray (which could be blit against anything) the cover
ages |
| 103 // remain the same. |
| 104 // |
| 105 // The idea here is that instead of determining the initial (real) coverage
and |
| 106 // then adjusting that coverage, we determine an adjusted coverage directly
by |
| 107 // essentially manipulating the geometry (in this case, the distance to the
glyph |
| 108 // edge). So for black (on assumed white) this thins a bit; for white (on |
| 109 // assumed black) this fake bolds the geometry a bit. |
| 110 // |
| 111 // The distance adjustment is calculated by determining the actual coverage
value which |
| 112 // when fed into in the mask gamma table gives us an 'adjusted coverage' val
ue of 0.5. This |
| 113 // actual coverage value (assuming it's between 0 and 1) corresponds to a di
stance from the |
| 114 // actual edge. So by subtracting this distance adjustment and computing wit
hout the |
| 115 // the coverage adjustment we should get 0.5 coverage at the same point. |
| 116 // |
| 117 // This has several implications: |
| 118 // For non-gray lcd smoothed text, each subpixel essentially is using a |
| 119 // slightly different geometry. |
| 120 // |
| 121 // For black (on assumed white) this may not cover some pixels which wer
e |
| 122 // previously covered; however those pixels would have been only slightl
y |
| 123 // covered and that slight coverage would have been decreased anyway. Al
so, some pixels |
| 124 // which were previously fully covered may no longer be fully covered. |
| 125 // |
| 126 // For white (on assumed black) this may cover some pixels which weren't |
| 127 // previously covered at all. |
| 128 |
| 129 int width, height; |
| 130 size_t size; |
| 131 |
| 132 #ifdef SK_GAMMA_CONTRAST |
| 133 SkScalar contrast = SK_GAMMA_CONTRAST; |
| 134 #else |
| 135 SkScalar contrast = 0.5f; |
| 136 #endif |
| 137 SkScalar paintGamma = fDeviceProperties.gamma(); |
| 138 SkScalar deviceGamma = fDeviceProperties.gamma(); |
| 139 |
| 140 size = SkScalerContext::GetGammaLUTSize(contrast, paintGamma, deviceGamma, |
| 141 &width, &height); |
| 142 |
| 143 SkASSERT(kExpectedDistanceAdjustTableSize == height); |
| 144 fDistanceAdjustTable = SkNEW_ARRAY(SkScalar, height); |
| 145 |
| 146 SkAutoTArray<uint8_t> data((int)size); |
| 147 SkScalerContext::GetGammaLUTData(contrast, paintGamma, deviceGamma, data.get
()); |
| 148 |
| 149 // find the inverse points where we cross 0.5 |
| 150 // binsearch might be better, but we only need to do this once on creation |
| 151 for (int row = 0; row < height; ++row) { |
| 152 uint8_t* rowPtr = data.get() + row*width; |
| 153 for (int col = 0; col < width - 1; ++col) { |
| 154 if (rowPtr[col] <= 127 && rowPtr[col + 1] >= 128) { |
| 155 // compute point where a mask value will give us a result of 0.5 |
| 156 float interp = (127.5f - rowPtr[col]) / (rowPtr[col + 1] - rowPt
r[col]); |
| 157 float borderAlpha = (col + interp) / 255.f; |
| 158 |
| 159 // compute t value for that alpha |
| 160 // this is an approximate inverse for smoothstep() |
| 161 float t = borderAlpha*(borderAlpha*(4.0f*borderAlpha - 6.0f) + 5
.0f) / 3.0f; |
| 162 |
| 163 // compute distance which gives us that t value |
| 164 const float kDistanceFieldAAFactor = 0.65f; // should match SK_D
istanceFieldAAFactor |
| 165 float d = 2.0f*kDistanceFieldAAFactor*t - kDistanceFieldAAFactor
; |
| 166 |
| 167 fDistanceAdjustTable[row] = d; |
| 168 break; |
| 169 } |
| 170 } |
| 171 } |
| 172 } |
| 173 |
| 174 |
86 GrDistanceFieldTextContext::~GrDistanceFieldTextContext() { | 175 GrDistanceFieldTextContext::~GrDistanceFieldTextContext() { |
87 SkSafeSetNull(fGammaTexture); | 176 SkDELETE_ARRAY(fDistanceAdjustTable); |
| 177 fDistanceAdjustTable = NULL; |
88 } | 178 } |
89 | 179 |
90 bool GrDistanceFieldTextContext::canDraw(const GrRenderTarget* rt, | 180 bool GrDistanceFieldTextContext::canDraw(const GrRenderTarget* rt, |
91 const GrClip& clip, | 181 const GrClip& clip, |
92 const GrPaint& paint, | 182 const GrPaint& paint, |
93 const SkPaint& skPaint, | 183 const SkPaint& skPaint, |
94 const SkMatrix& viewMatrix) { | 184 const SkMatrix& viewMatrix) { |
95 // TODO: support perspective (need getMaxScale replacement) | 185 // TODO: support perspective (need getMaxScale replacement) |
96 if (viewMatrix.hasPerspective()) { | 186 if (viewMatrix.hasPerspective()) { |
97 return false; | 187 return false; |
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
175 } | 265 } |
176 | 266 |
177 fUseLCDText = fSkPaint.isLCDRenderText(); | 267 fUseLCDText = fSkPaint.isLCDRenderText(); |
178 | 268 |
179 fSkPaint.setLCDRenderText(false); | 269 fSkPaint.setLCDRenderText(false); |
180 fSkPaint.setAutohinted(false); | 270 fSkPaint.setAutohinted(false); |
181 fSkPaint.setHinting(SkPaint::kNormal_Hinting); | 271 fSkPaint.setHinting(SkPaint::kNormal_Hinting); |
182 fSkPaint.setSubpixelText(true); | 272 fSkPaint.setSubpixelText(true); |
183 } | 273 } |
184 | 274 |
185 static void setup_gamma_texture(GrContext* context, const SkGlyphCache* cache, | |
186 const SkDeviceProperties& deviceProperties, | |
187 GrTexture** gammaTexture) { | |
188 if (NULL == *gammaTexture) { | |
189 int width, height; | |
190 size_t size; | |
191 | |
192 #ifdef SK_GAMMA_CONTRAST | |
193 SkScalar contrast = SK_GAMMA_CONTRAST; | |
194 #else | |
195 SkScalar contrast = 0.5f; | |
196 #endif | |
197 SkScalar paintGamma = deviceProperties.gamma(); | |
198 SkScalar deviceGamma = deviceProperties.gamma(); | |
199 | |
200 size = SkScalerContext::GetGammaLUTSize(contrast, paintGamma, deviceGamm
a, | |
201 &width, &height); | |
202 | |
203 SkAutoTArray<uint8_t> data((int)size); | |
204 SkScalerContext::GetGammaLUTData(contrast, paintGamma, deviceGamma, data
.get()); | |
205 | |
206 // TODO: Update this to use the cache rather than directly creating a te
xture. | |
207 GrSurfaceDesc desc; | |
208 desc.fFlags = kNone_GrSurfaceFlags; | |
209 desc.fWidth = width; | |
210 desc.fHeight = height; | |
211 desc.fConfig = kAlpha_8_GrPixelConfig; | |
212 | |
213 *gammaTexture = context->getGpu()->createTexture(desc, true, NULL, 0); | |
214 if (NULL == *gammaTexture) { | |
215 return; | |
216 } | |
217 | |
218 (*gammaTexture)->writePixels(0, 0, width, height, | |
219 (*gammaTexture)->config(), data.get(), 0, | |
220 GrContext::kDontFlush_PixelOpsFlag); | |
221 } | |
222 } | |
223 | |
224 void GrDistanceFieldTextContext::onDrawText(GrRenderTarget* rt, const GrClip& cl
ip, | 275 void GrDistanceFieldTextContext::onDrawText(GrRenderTarget* rt, const GrClip& cl
ip, |
225 const GrPaint& paint, | 276 const GrPaint& paint, |
226 const SkPaint& skPaint, const SkMatr
ix& viewMatrix, | 277 const SkPaint& skPaint, const SkMatr
ix& viewMatrix, |
227 const char text[], size_t byteLength
, | 278 const char text[], size_t byteLength
, |
228 SkScalar x, SkScalar y, | 279 SkScalar x, SkScalar y, |
229 const SkIRect& regionClipBounds) { | 280 const SkIRect& regionClipBounds) { |
230 SkASSERT(byteLength == 0 || text != NULL); | 281 SkASSERT(byteLength == 0 || text != NULL); |
231 | 282 |
232 // nothing to draw | 283 // nothing to draw |
233 if (text == NULL || byteLength == 0) { | 284 if (text == NULL || byteLength == 0) { |
(...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
306 | 357 |
307 fViewMatrix = viewMatrix; | 358 fViewMatrix = viewMatrix; |
308 this->init(rt, clip, paint, skPaint, regionClipBounds); | 359 this->init(rt, clip, paint, skPaint, regionClipBounds); |
309 | 360 |
310 SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc(); | 361 SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc(); |
311 | 362 |
312 SkAutoGlyphCacheNoGamma autoCache(fSkPaint, &fDeviceProperties, NULL); | 363 SkAutoGlyphCacheNoGamma autoCache(fSkPaint, &fDeviceProperties, NULL); |
313 SkGlyphCache* cache = autoCache.getCache(); | 364 SkGlyphCache* cache = autoCache.getCache(); |
314 GrFontScaler* fontScaler = GetGrFontScaler(cache); | 365 GrFontScaler* fontScaler = GetGrFontScaler(cache); |
315 | 366 |
316 setup_gamma_texture(fContext, cache, fDeviceProperties, &fGammaTexture); | |
317 | |
318 int numGlyphs = fSkPaint.textToGlyphs(text, byteLength, NULL); | 367 int numGlyphs = fSkPaint.textToGlyphs(text, byteLength, NULL); |
319 fTotalVertexCount = kVerticesPerGlyph*numGlyphs; | 368 fTotalVertexCount = kVerticesPerGlyph*numGlyphs; |
320 | 369 |
321 const char* stop = text + byteLength; | 370 const char* stop = text + byteLength; |
322 SkTArray<char> fallbackTxt; | 371 SkTArray<char> fallbackTxt; |
323 SkTArray<SkScalar> fallbackPos; | 372 SkTArray<SkScalar> fallbackPos; |
324 | 373 |
325 if (SkPaint::kLeft_Align == fSkPaint.getTextAlign()) { | 374 if (SkPaint::kLeft_Align == fSkPaint.getTextAlign()) { |
326 while (text < stop) { | 375 while (text < stop) { |
327 const char* lastText = text; | 376 const char* lastText = text; |
(...skipping 107 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
435 flags |= fUseLCDText && useBGR ? kBGR_DistanceFieldEffectFlag : 0; | 484 flags |= fUseLCDText && useBGR ? kBGR_DistanceFieldEffectFlag : 0; |
436 | 485 |
437 // see if we need to create a new effect | 486 // see if we need to create a new effect |
438 if (textureUniqueID != fEffectTextureUniqueID || | 487 if (textureUniqueID != fEffectTextureUniqueID || |
439 filteredColor != fEffectColor || | 488 filteredColor != fEffectColor || |
440 flags != fEffectFlags || | 489 flags != fEffectFlags || |
441 !fCachedGeometryProcessor->viewMatrix().cheapEqualTo(fViewMatrix)) { | 490 !fCachedGeometryProcessor->viewMatrix().cheapEqualTo(fViewMatrix)) { |
442 GrColor color = fPaint.getColor(); | 491 GrColor color = fPaint.getColor(); |
443 if (fUseLCDText) { | 492 if (fUseLCDText) { |
444 GrColor colorNoPreMul = skcolor_to_grcolor_nopremultiply(filteredCol
or); | 493 GrColor colorNoPreMul = skcolor_to_grcolor_nopremultiply(filteredCol
or); |
| 494 |
| 495 float redCorrection = |
| 496 fDistanceAdjustTable[GrColorUnpackR(colorNoPreMul) >> kDistanceA
djustLumShift]; |
| 497 float greenCorrection = |
| 498 fDistanceAdjustTable[GrColorUnpackG(colorNoPreMul) >> kDistanceA
djustLumShift]; |
| 499 float blueCorrection = |
| 500 fDistanceAdjustTable[GrColorUnpackB(colorNoPreMul) >> kDistanceA
djustLumShift]; |
| 501 GrDistanceFieldLCDTextureEffect::DistanceAdjust widthAdjust = |
| 502 GrDistanceFieldLCDTextureEffect::DistanceAdjust::Make(redCorrect
ion, |
| 503 greenCorrecti
on, |
| 504 blueCorrectio
n); |
445 fCachedGeometryProcessor.reset(GrDistanceFieldLCDTextureEffect::Crea
te(color, | 505 fCachedGeometryProcessor.reset(GrDistanceFieldLCDTextureEffect::Crea
te(color, |
446
fViewMatrix, | 506
fViewMatrix, |
447
fCurrTexture, | 507
fCurrTexture, |
448
params, | 508
params, |
449
fGammaTexture, | 509
widthAdjust, |
450
gammaParams, | |
451
colorNoPreMul, | |
452
flags)); | 510
flags)); |
453 } else { | 511 } else { |
454 flags |= kColorAttr_DistanceFieldEffectFlag; | 512 flags |= kColorAttr_DistanceFieldEffectFlag; |
455 bool opaque = GrColorIsOpaque(color); | 513 bool opaque = GrColorIsOpaque(color); |
456 #ifdef SK_GAMMA_APPLY_TO_A8 | 514 #ifdef SK_GAMMA_APPLY_TO_A8 |
457 U8CPU lum = SkColorSpaceLuminance::computeLuminance(fDevicePropertie
s.gamma(), | 515 U8CPU lum = SkColorSpaceLuminance::computeLuminance(fDevicePropertie
s.gamma(), |
458 filteredColor); | 516 filteredColor); |
| 517 float correction = fDistanceAdjustTable[lum >> kDistanceAdjustLumShi
ft]; |
459 fCachedGeometryProcessor.reset(GrDistanceFieldTextureEffect::Create(
color, | 518 fCachedGeometryProcessor.reset(GrDistanceFieldTextureEffect::Create(
color, |
460
fViewMatrix, | 519
fViewMatrix, |
461
fCurrTexture, | 520
fCurrTexture, |
462
params, | 521
params, |
463
fGammaTexture, | 522
correction, |
464
gammaParams, | |
465
lum/255.f, | |
466
flags, | 523
flags, |
467
opaque)); | 524
opaque)); |
468 #else | 525 #else |
469 fCachedGeometryProcessor.reset(GrDistanceFieldTextureEffect::Create(
color, | 526 fCachedGeometryProcessor.reset(GrDistanceFieldTextureEffect::Create(
color, |
470
fViewMatrix, | 527
fViewMatrix, |
471
fCurrTexture, | 528
fCurrTexture, |
472
params, | 529
params, |
473
flags, | 530
flags, |
474
opaque)); | 531
opaque)); |
475 #endif | 532 #endif |
(...skipping 260 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
736 } | 793 } |
737 } | 794 } |
738 | 795 |
739 inline void GrDistanceFieldTextContext::finish() { | 796 inline void GrDistanceFieldTextContext::finish() { |
740 this->flush(); | 797 this->flush(); |
741 fTotalVertexCount = 0; | 798 fTotalVertexCount = 0; |
742 | 799 |
743 GrTextContext::finish(); | 800 GrTextContext::finish(); |
744 } | 801 } |
745 | 802 |
OLD | NEW |