From 22253064ceee1dd9fcd26b7d6d69505cba76bb33 Mon Sep 17 00:00:00 2001 From: Ben Wagner Date: Thu, 16 Mar 2017 12:38:46 -0400 Subject: Support pixel antialising in DirectWrite. DirectWrite2 supports pixel antialiasing and rendering without hinting. To maintain the infered size based decisions of the previous rendering, only use the new renderer when it is needed. BUG=skia:5416 Change-Id: Ia87fdf8fc91dc7ff0f0ce4284c90a5d79045308f Reviewed-on: https://skia-review.googlesource.com/9805 Commit-Queue: Ben Wagner Reviewed-by: Ben Wagner --- src/ports/SkScalerContext_win_dw.cpp | 256 +++++++++++++++++++++-------------- src/ports/SkScalerContext_win_dw.h | 3 +- src/ports/SkTypeface_win_dw.cpp | 7 +- src/ports/SkTypeface_win_dw.h | 9 ++ 4 files changed, 172 insertions(+), 103 deletions(-) diff --git a/src/ports/SkScalerContext_win_dw.cpp b/src/ports/SkScalerContext_win_dw.cpp index d3cea9d642..33ee9dce7d 100644 --- a/src/ports/SkScalerContext_win_dw.cpp +++ b/src/ports/SkScalerContext_win_dw.cpp @@ -68,35 +68,35 @@ static bool is_hinted_without_gasp(DWriteFontTypeface* typeface) { return !gasp.fExists; } -/** A PPEMRange is inclusive, [min, max]. */ -struct PPEMRange { - int min; - int max; +/** A GaspRange is inclusive, [min, max]. */ +struct GaspRange { + using Behavior = SkOTTableGridAndScanProcedure::GaspRange::behavior; + GaspRange(int min, int max, Behavior flags) : fMin(min), fMax(max), fFlags(flags) { } + int fMin; + int fMax; + Behavior fFlags; }; -/** If the rendering mode for the specified 'size' is gridfit, then place - * the gridfit range into 'range'. Otherwise, leave 'range' alone. - */ -static void expand_range_if_gridfit_only(DWriteFontTypeface* typeface, int size, PPEMRange* range) { +bool get_gasp_range(DWriteFontTypeface* typeface, int size, GaspRange* range) { AutoTDWriteTable gasp(typeface->fDWriteFontFace.get()); if (!gasp.fExists) { - return; + return false; } if (gasp.fSize < sizeof(SkOTTableGridAndScanProcedure)) { - return; + return false; } if (gasp->version != SkOTTableGridAndScanProcedure::version0 && gasp->version != SkOTTableGridAndScanProcedure::version1) { - return; + return false; } uint16_t numRanges = SkEndianSwap16(gasp->numRanges); if (numRanges > 1024 || gasp.fSize < sizeof(SkOTTableGridAndScanProcedure) + - sizeof(SkOTTableGridAndScanProcedure::GaspRange) * numRanges) + sizeof(SkOTTableGridAndScanProcedure::GaspRange) * numRanges) { - return; + return false; } const SkOTTableGridAndScanProcedure::GaspRange* rangeTable = @@ -104,58 +104,32 @@ static void expand_range_if_gridfit_only(DWriteFontTypeface* typeface, int size, int minPPEM = -1; for (uint16_t i = 0; i < numRanges; ++i, ++rangeTable) { int maxPPEM = SkEndianSwap16(rangeTable->maxPPEM); - // Test that the size is in range and the range is gridfit only. - if (minPPEM < size && size <= maxPPEM && - rangeTable->flags.raw.value == SkOTTableGridAndScanProcedure::GaspRange::behavior::Raw::GridfitMask) - { - range->min = minPPEM + 1; - range->max = maxPPEM; - return; + if (minPPEM < size && size <= maxPPEM) { + range->fMin = minPPEM + 1; + range->fMax = maxPPEM; + range->fFlags = rangeTable->flags; + return true; } minPPEM = maxPPEM; } + return false; +} +/** If the rendering mode for the specified 'size' is gridfit, then place + * the gridfit range into 'range'. Otherwise, leave 'range' alone. + */ +static bool is_gridfit_only(GaspRange::Behavior flags) { + return flags.raw.value == GaspRange::Behavior::Raw::GridfitMask; } /** If the rendering mode for the specified 'size' sets SymmetricSmoothing, return true. */ -static bool gasp_allows_cleartype_symmetric(DWriteFontTypeface* typeface, int size) { +static bool gasp_allows_cleartype_symmetric(GaspRange::Behavior flags) { #ifdef SK_IGNORE_DIRECTWRITE_GASP_FIX return true; #endif - AutoTDWriteTable gasp(typeface->fDWriteFontFace.get()); - if (!gasp.fExists) { - return false; - } - if (gasp.fSize < sizeof(SkOTTableGridAndScanProcedure)) { - return false; - } - if (gasp->version != SkOTTableGridAndScanProcedure::version0 && - gasp->version != SkOTTableGridAndScanProcedure::version1) - { - return false; - } - - uint16_t numRanges = SkEndianSwap16(gasp->numRanges); - if (numRanges > 1024 || - gasp.fSize < sizeof(SkOTTableGridAndScanProcedure) + - sizeof(SkOTTableGridAndScanProcedure::GaspRange) * numRanges) - { - return false; - } - - const SkOTTableGridAndScanProcedure::GaspRange* rangeTable = - SkTAfter(gasp.get()); - int minPPEM = -1; - for (uint16_t i = 0; i < numRanges; ++i, ++rangeTable) { - int maxPPEM = SkEndianSwap16(rangeTable->maxPPEM); - if (minPPEM < size && size <= maxPPEM) { - return rangeTable->flags.field.SymmetricSmoothing; - } - minPPEM = maxPPEM; - } - return false; + return flags.field.SymmetricSmoothing; } -static bool has_bitmap_strike(DWriteFontTypeface* typeface, PPEMRange range) { +static bool has_bitmap_strike(DWriteFontTypeface* typeface, GaspRange range) { SkAutoExclusive l(DWriteFactoryMutex); { AutoTDWriteTable eblc(typeface->fDWriteFontFace.get()); @@ -181,7 +155,7 @@ static bool has_bitmap_strike(DWriteFontTypeface* typeface, PPEMRange range) { SkTAfter(eblc.get()); for (uint32_t i = 0; i < numSizes; ++i, ++sizeTable) { if (sizeTable->ppemX == sizeTable->ppemY && - range.min <= sizeTable->ppemX && sizeTable->ppemX <= range.max) + range.fMin <= sizeTable->ppemX && sizeTable->ppemX <= range.fMax) { // TODO: determine if we should dig through IndexSubTableArray/IndexSubTable // to determine the actual number of glyphs with bitmaps. @@ -220,7 +194,7 @@ static bool has_bitmap_strike(DWriteFontTypeface* typeface, PPEMRange range) { SkTAfter(ebsc.get()); for (uint32_t i = 0; i < numSizes; ++i, ++scaleTable) { if (scaleTable->ppemX == scaleTable->ppemY && - range.min <= scaleTable->ppemX && scaleTable->ppemX <= range.max) { + range.fMin <= scaleTable->ppemX && scaleTable->ppemX <= range.fMax) { // EBSC tables are normally only found in bitmap only fonts. return true; } @@ -248,13 +222,11 @@ SkScalerContext_DW::SkScalerContext_DW(sk_sp typefaceRef, , fGlyphCount(-1) { DWriteFontTypeface* typeface = this->getDWriteTypeface(); - typeface->fFactory->QueryInterface(&fFactory2); - - SkTScopedComPtr fontFace2; - typeface->fDWriteFontFace->QueryInterface(&fontFace2); - fIsColorFont = fFactory2.get() && fontFace2.get() && fontFace2->IsColorFont(); + fIsColorFont = typeface->fFactory2 && + typeface->fDWriteFontFace2 && + typeface->fDWriteFontFace2->IsColorFont(); - // In general, all glyphs should use CLEARTYPE_NATURAL_SYMMETRIC + // In general, all glyphs should use NATURAL_SYMMETRIC // except when bi-level rendering is requested or there are embedded // bi-level bitmaps (and the embedded bitmap flag is set and no rotation). // @@ -306,8 +278,12 @@ SkScalerContext_DW::SkScalerContext_DW(sk_sp typefaceRef, // When embedded bitmaps are requested, treat the entire range like // a bitmap strike if the range is gridfit only and contains a bitmap. int bitmapPPEM = SkScalarTruncToInt(gdiTextSize); - PPEMRange range = { bitmapPPEM, bitmapPPEM }; - expand_range_if_gridfit_only(typeface, bitmapPPEM, &range); + GaspRange range(bitmapPPEM, bitmapPPEM, GaspRange::Behavior()); + if (get_gasp_range(typeface, bitmapPPEM, &range)) { + if (!is_gridfit_only(range.fFlags)) { + range = GaspRange(bitmapPPEM, bitmapPPEM, GaspRange::Behavior()); + } + } treatLikeBitmap = has_bitmap_strike(typeface, range); axisAlignedBitmap = is_axis_aligned(fRec); @@ -325,7 +301,7 @@ SkScalerContext_DW::SkScalerContext_DW(sk_sp typefaceRef, // This will not always provide a bitmap, but matches expected behavior. } else if (treatLikeBitmap && axisAlignedBitmap) { fTextSizeRender = gdiTextSize; - fRenderingMode = DWRITE_RENDERING_MODE_CLEARTYPE_GDI_CLASSIC; + fRenderingMode = DWRITE_RENDERING_MODE_GDI_CLASSIC; fTextureType = DWRITE_TEXTURE_CLEARTYPE_3x1; fTextSizeMeasure = gdiTextSize; fMeasuringMode = DWRITE_MEASURING_MODE_GDI_CLASSIC; @@ -334,7 +310,7 @@ SkScalerContext_DW::SkScalerContext_DW(sk_sp typefaceRef, // render high quality rotated glyphs but measure using bitmap metrics. } else if (treatLikeBitmap) { fTextSizeRender = gdiTextSize; - fRenderingMode = DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL_SYMMETRIC; + fRenderingMode = DWRITE_RENDERING_MODE_NATURAL_SYMMETRIC; fTextureType = DWRITE_TEXTURE_CLEARTYPE_3x1; fTextSizeMeasure = gdiTextSize; fMeasuringMode = DWRITE_MEASURING_MODE_GDI_CLASSIC; @@ -345,7 +321,7 @@ SkScalerContext_DW::SkScalerContext_DW(sk_sp typefaceRef, // drop out control in the y direction in order to be legible. } else if (is_hinted_without_gasp(typeface)) { fTextSizeRender = gdiTextSize; - fRenderingMode = DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL; + fRenderingMode = DWRITE_RENDERING_MODE_NATURAL; fTextureType = DWRITE_TEXTURE_CLEARTYPE_3x1; fTextSizeMeasure = realTextSize; fMeasuringMode = DWRITE_MEASURING_MODE_NATURAL; @@ -353,14 +329,37 @@ SkScalerContext_DW::SkScalerContext_DW(sk_sp typefaceRef, // The normal case is to use natural symmetric rendering (if permitted) and linear metrics. } else { fTextSizeRender = realTextSize; - fRenderingMode = gasp_allows_cleartype_symmetric(typeface, realTextSize) - ? DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL_SYMMETRIC - : DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL; + GaspRange range(0, 0xFFFF, GaspRange::Behavior()); + get_gasp_range(typeface, SkScalarTruncToInt(fTextSizeRender), &range); + fRenderingMode = gasp_allows_cleartype_symmetric(range.fFlags) + ? DWRITE_RENDERING_MODE_NATURAL_SYMMETRIC + : DWRITE_RENDERING_MODE_NATURAL; fTextureType = DWRITE_TEXTURE_CLEARTYPE_3x1; fTextSizeMeasure = realTextSize; fMeasuringMode = DWRITE_MEASURING_MODE_NATURAL; } + // DirectWrite2 allows for grayscale hinting. + fAntiAliasMode = DWRITE_TEXT_ANTIALIAS_MODE_CLEARTYPE; +#ifndef SK_IGNORE_DW_GRAY_FIX + if (typeface->fFactory2 && typeface->fDWriteFontFace2 && + !isLCD(fRec) && !(fRec.fFlags & SkScalerContext::kGenA8FromLCD_Flag)) + { + // DWRITE_TEXTURE_ALIASED_1x1 is now misnamed, it must also be used with grayscale. + fTextureType = DWRITE_TEXTURE_ALIASED_1x1; + fAntiAliasMode = DWRITE_TEXT_ANTIALIAS_MODE_GRAYSCALE; + } +#endif + + // DirectWrite2 allows hinting to be disabled. + fGridFitMode = DWRITE_GRID_FIT_MODE_ENABLED; + if (fRec.getHinting() == SkPaint::kNo_Hinting) { + fGridFitMode = DWRITE_GRID_FIT_MODE_DISABLED; + if (fRenderingMode != DWRITE_RENDERING_MODE_ALIASED) { + fRenderingMode = DWRITE_RENDERING_MODE_NATURAL_SYMMETRIC; + } + } + if (this->isSubpixel()) { fTextSizeMeasure = realTextSize; fMeasuringMode = DWRITE_MEASURING_MODE_NATURAL; @@ -468,16 +467,33 @@ HRESULT SkScalerContext_DW::getBoundingBox(SkGlyph* glyph, SkTScopedComPtr glyphRunAnalysis; { SkAutoExclusive l(DWriteFactoryMutex); - HRM(this->getDWriteTypeface()->fFactory->CreateGlyphRunAnalysis( - &run, - 1.0f, // pixelsPerDip, - &fXform, - renderingMode, - fMeasuringMode, - 0.0f, // baselineOriginX, - 0.0f, // baselineOriginY, - &glyphRunAnalysis), - "Could not create glyph run analysis."); + // IDWriteFactory2::CreateGlyphRunAnalysis is very bad at aliased glyphs. + if (this->getDWriteTypeface()->fFactory2 && + (fGridFitMode == DWRITE_GRID_FIT_MODE_DISABLED || + fAntiAliasMode == DWRITE_TEXT_ANTIALIAS_MODE_GRAYSCALE)) + { + HRM(this->getDWriteTypeface()->fFactory2->CreateGlyphRunAnalysis( + &run, + &fXform, + renderingMode, + fMeasuringMode, + fGridFitMode, + fAntiAliasMode, + 0.0f, // baselineOriginX, + 0.0f, // baselineOriginY, + &glyphRunAnalysis), + "Could not create DW2 glyph run analysis."); + } else { + HRM(this->getDWriteTypeface()->fFactory->CreateGlyphRunAnalysis(&run, + 1.0f, // pixelsPerDip, + &fXform, + renderingMode, + fMeasuringMode, + 0.0f, // baselineOriginX, + 0.0f, // baselineOriginY, + &glyphRunAnalysis), + "Could not create glyph run analysis."); + } } { Shared l(DWriteFactoryMutex); @@ -528,7 +544,7 @@ bool SkScalerContext_DW::getColorGlyphRun(const SkGlyph& glyph, run.isSideways = FALSE; run.glyphOffsets = &offset; - HRESULT hr = fFactory2->TranslateColorGlyphRun( + HRESULT hr = this->getDWriteTypeface()->fFactory2->TranslateColorGlyphRun( 0, 0, &run, nullptr, fMeasuringMode, &fXform, 0, colorGlyph); if (hr == DWRITE_E_NOCOLOR) { return false; @@ -679,6 +695,22 @@ static void bilevel_to_bw(const uint8_t* SK_RESTRICT src, const SkGlyph& glyph) } } +template +static void grayscale_to_a8(const uint8_t* SK_RESTRICT src, const SkGlyph& glyph, + const uint8_t* table8) { + const size_t dstRB = glyph.rowBytes(); + const U16CPU width = glyph.fWidth; + uint8_t* SK_RESTRICT dst = static_cast(glyph.fImage); + + for (U16CPU y = 0; y < glyph.fHeight; y++) { + for (U16CPU i = 0; i < width; i++) { + U8CPU a = *(src++); + dst[i] = sk_apply_lut_if(a, table8); + } + dst = SkTAddOffset(dst, dstRB); + } +} + template static void rgb_to_a8(const uint8_t* SK_RESTRICT src, const SkGlyph& glyph, const uint8_t* table8) { const size_t dstRB = glyph.rowBytes(); @@ -692,7 +724,7 @@ static void rgb_to_a8(const uint8_t* SK_RESTRICT src, const SkGlyph& glyph, cons U8CPU b = *(src++); dst[i] = sk_apply_lut_if((r + g + b) / 3, table8); } - dst = (uint8_t*)((char*)dst + dstRB); + dst = SkTAddOffset(dst, dstRB); } } @@ -717,7 +749,7 @@ static void rgb_to_lcd16(const uint8_t* SK_RESTRICT src, const SkGlyph& glyph, } dst[i] = SkPack888ToRGB16(r, g, b); } - dst = (uint16_t*)((char*)dst + dstRB); + dst = SkTAddOffset(dst, dstRB); } } @@ -726,7 +758,7 @@ const void* SkScalerContext_DW::drawDWMask(const SkGlyph& glyph, DWRITE_TEXTURE_TYPE textureType) { int sizeNeeded = glyph.fWidth * glyph.fHeight; - if (DWRITE_RENDERING_MODE_ALIASED != renderingMode) { + if (DWRITE_TEXTURE_CLEARTYPE_3x1 == textureType) { sizeNeeded *= 3; } if (sizeNeeded > fBits.count()) { @@ -757,19 +789,35 @@ const void* SkScalerContext_DW::drawDWMask(const SkGlyph& glyph, run.isSideways = FALSE; run.glyphOffsets = &offset; { - SkTScopedComPtr glyphRunAnalysis; { SkAutoExclusive l(DWriteFactoryMutex); - HRNM(this->getDWriteTypeface()->fFactory->CreateGlyphRunAnalysis(&run, - 1.0f, // pixelsPerDip, - &fXform, - renderingMode, - fMeasuringMode, - 0.0f, // baselineOriginX, - 0.0f, // baselineOriginY, - &glyphRunAnalysis), - "Could not create glyph run analysis."); + // IDWriteFactory2::CreateGlyphRunAnalysis is very bad at aliased glyphs. + if (this->getDWriteTypeface()->fFactory2 && + (fGridFitMode == DWRITE_GRID_FIT_MODE_DISABLED || + fAntiAliasMode == DWRITE_TEXT_ANTIALIAS_MODE_GRAYSCALE)) + { + HRNM(this->getDWriteTypeface()->fFactory2->CreateGlyphRunAnalysis(&run, + &fXform, + renderingMode, + fMeasuringMode, + fGridFitMode, + fAntiAliasMode, + 0.0f, // baselineOriginX, + 0.0f, // baselineOriginY, + &glyphRunAnalysis), + "Could not create DW2 glyph run analysis."); + } else { + HRNM(this->getDWriteTypeface()->fFactory->CreateGlyphRunAnalysis(&run, + 1.0f, // pixelsPerDip, + &fXform, + renderingMode, + fMeasuringMode, + 0.0f, // baselineOriginX, + 0.0f, // baselineOriginY, + &glyphRunAnalysis), + "Could not create glyph run analysis."); + } } //NOTE: this assumes that the glyph has already been measured //with an exact same glyph run analysis. @@ -781,9 +829,9 @@ const void* SkScalerContext_DW::drawDWMask(const SkGlyph& glyph, { Shared l(DWriteFactoryMutex); HRNM(glyphRunAnalysis->CreateAlphaTexture(textureType, - &bbox, - fBits.begin(), - sizeNeeded), + &bbox, + fBits.begin(), + sizeNeeded), "Could not draw mask."); } } @@ -883,10 +931,18 @@ void SkScalerContext_DW::generateImage(const SkGlyph& glyph) { bilevel_to_bw(src, glyph); const_cast(glyph).fMaskFormat = SkMask::kBW_Format; } else if (!isLCD(fRec)) { - if (fPreBlend.isApplicable()) { - rgb_to_a8(src, glyph, fPreBlend.fG); + if (textureType == DWRITE_TEXTURE_ALIASED_1x1) { + if (fPreBlend.isApplicable()) { + grayscale_to_a8(src, glyph, fPreBlend.fG); + } else { + grayscale_to_a8(src, glyph, fPreBlend.fG); + } } else { - rgb_to_a8(src, glyph, fPreBlend.fG); + if (fPreBlend.isApplicable()) { + rgb_to_a8(src, glyph, fPreBlend.fG); + } else { + rgb_to_a8(src, glyph, fPreBlend.fG); + } } } else { SkASSERT(SkMask::kLCD16_Format == glyph.fMaskFormat); diff --git a/src/ports/SkScalerContext_win_dw.h b/src/ports/SkScalerContext_win_dw.h index e13e01a61b..f186ea52dc 100644 --- a/src/ports/SkScalerContext_win_dw.h +++ b/src/ports/SkScalerContext_win_dw.h @@ -76,7 +76,8 @@ private: DWRITE_RENDERING_MODE fRenderingMode; DWRITE_TEXTURE_TYPE fTextureType; DWRITE_MEASURING_MODE fMeasuringMode; - SkTScopedComPtr fFactory2; + DWRITE_TEXT_ANTIALIAS_MODE fAntiAliasMode; + DWRITE_GRID_FIT_MODE fGridFitMode; bool fIsColorFont; }; diff --git a/src/ports/SkTypeface_win_dw.cpp b/src/ports/SkTypeface_win_dw.cpp index 3acdd213ac..7d18da9d5a 100644 --- a/src/ports/SkTypeface_win_dw.cpp +++ b/src/ports/SkTypeface_win_dw.cpp @@ -253,6 +253,7 @@ SkScalerContext* DWriteFontTypeface::onCreateScalerContext(const SkScalerContext void DWriteFontTypeface::onFilterRec(SkScalerContext::Rec* rec) const { if (rec->fFlags & SkScalerContext::kLCD_Vertical_Flag) { rec->fMaskFormat = SkMask::kA8_Format; + rec->fFlags |= SkScalerContext::kGenA8FromLCD_Flag; } unsigned flagsWeDontSupport = SkScalerContext::kVertical_Flag | @@ -263,8 +264,10 @@ void DWriteFontTypeface::onFilterRec(SkScalerContext::Rec* rec) const { rec->fFlags &= ~flagsWeDontSupport; SkPaint::Hinting h = rec->getHinting(); - // DirectWrite does not provide for hinting hints. - h = SkPaint::kSlight_Hinting; + // DirectWrite2 allows for hinting to be turned off. Force everything else to normal. + if (h != SkPaint::kNo_Hinting || !fFactory2 || !fDWriteFontFace2) { + h = SkPaint::kNormal_Hinting; + } rec->setHinting(h); #if defined(SK_FONT_HOST_USE_SYSTEM_SETTINGS) diff --git a/src/ports/SkTypeface_win_dw.h b/src/ports/SkTypeface_win_dw.h index d4d4facab1..d7c73e2697 100644 --- a/src/ports/SkTypeface_win_dw.h +++ b/src/ports/SkTypeface_win_dw.h @@ -18,6 +18,7 @@ #include #include +#include class SkFontDescriptor; struct SkScalerContextRec; @@ -57,16 +58,24 @@ private: // http://blogs.msdn.com/b/oldnewthing/archive/2004/03/26/96777.aspx SkASSERT_RELEASE(nullptr == fDWriteFontFace1.get()); } + if (!SUCCEEDED(fDWriteFontFace->QueryInterface(&fDWriteFontFace2))) { + SkASSERT_RELEASE(nullptr == fDWriteFontFace2.get()); + } + if (!SUCCEEDED(fFactory->QueryInterface(&fFactory2))) { + SkASSERT_RELEASE(nullptr == fFactory2.get()); + } } public: SkTScopedComPtr fFactory; + SkTScopedComPtr fFactory2; SkTScopedComPtr fDWriteFontCollectionLoader; SkTScopedComPtr fDWriteFontFileLoader; SkTScopedComPtr fDWriteFontFamily; SkTScopedComPtr fDWriteFont; SkTScopedComPtr fDWriteFontFace; SkTScopedComPtr fDWriteFontFace1; + SkTScopedComPtr fDWriteFontFace2; static DWriteFontTypeface* Create(IDWriteFactory* factory, IDWriteFontFace* fontFace, -- cgit v1.2.3