From 34db1eee20ffea6e423284bb06824653efff645d Mon Sep 17 00:00:00 2001 From: Ben Wagner Date: Wed, 15 Mar 2017 15:22:29 -0400 Subject: [PATCH] Support pixel antialising in DirectWrite. DirectWrite2 supports pixel antialiasing and rendering without hinting. BUG=skia:5416 Change-Id: I215245b20dd403669dbccd37e34cb2fcd5e06431 Reviewed-on: https://skia-review.googlesource.com/9145 Reviewed-by: Mike Reed Commit-Queue: Ben Wagner --- src/ports/SkScalerContext_win_dw.cpp | 246 ++++++++++++++++++++++------------- src/ports/SkScalerContext_win_dw.h | 3 +- src/ports/SkTypeface_win_dw.cpp | 7 +- src/ports/SkTypeface_win_dw.h | 9 ++ 4 files changed, 170 insertions(+), 95 deletions(-) diff --git a/src/ports/SkScalerContext_win_dw.cpp b/src/ports/SkScalerContext_win_dw.cpp index d3cea9d..b2a61cc 100644 --- a/src/ports/SkScalerContext_win_dw.cpp +++ b/src/ports/SkScalerContext_win_dw.cpp @@ -68,35 +68,33 @@ static bool is_hinted_without_gasp(DWriteFontTypeface* typeface) { return !gasp.fExists; } -/** A PPEMRange is inclusive, [min, max]. */ -struct PPEMRange { +/** A GaspRange is inclusive, [min, max]. */ +struct GaspRange { int min; int max; + SkOTTableGridAndScanProcedure::GaspRange::behavior flags; }; -/** 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 +102,40 @@ 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) - { + if (minPPEM < size && size <= maxPPEM) { range->min = minPPEM + 1; range->max = maxPPEM; - return; + range->flags = 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(SkOTTableGridAndScanProcedure::GaspRange::behavior flags) { + return flags.raw.value == SkOTTableGridAndScanProcedure::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(SkOTTableGridAndScanProcedure::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; - } + return flags.field.SymmetricSmoothing; +} - 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; +/** If gidfitting is enabled at this size, return true. */ +static bool gasp_allows_gridfit(SkOTTableGridAndScanProcedure::GaspRange::behavior flags, DWRITE_RENDERING_MODE renderingMode) { + if (renderingMode == DWRITE_RENDERING_MODE_NATURAL_SYMMETRIC) { + return flags.field.SymmetricGridfit; } - return false; + return flags.field.Gridfit; } -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()); @@ -248,13 +228,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 +284,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, 0}; + if (get_gasp_range(typeface, bitmapPPEM, &range)) { + if (!is_gridfit_only(range.flags)) { + range = GaspRange{bitmapPPEM, bitmapPPEM, 0}; + } + } treatLikeBitmap = has_bitmap_strike(typeface, range); axisAlignedBitmap = is_axis_aligned(fRec); @@ -325,7 +307,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 +316,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 +327,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 +335,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, 0}; + get_gasp_range(typeface, SkScalarTruncToInt(fTextSizeRender), &range); + fRenderingMode = gasp_allows_cleartype_symmetric(range.flags) + ? 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 +473,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 +550,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; @@ -680,6 +702,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(); const U16CPU width = glyph.fWidth; @@ -692,7 +730,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 +755,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 +764,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 +795,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 +835,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 +937,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 e13e01a..f186ea5 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 3acdd21..7d18da9 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 d4d4fac..d7c73e2 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, -- 2.7.4