SkShader* SkPictureShader::refBitmapShader(const SkMatrix& matrix, const SkMatrix* localM) const { SkASSERT(fPicture && fPicture->width() > 0 && fPicture->height() > 0); SkMatrix m; m.setConcat(matrix, this->getLocalMatrix()); if (localM) { m.preConcat(*localM); } // Use a rotation-invariant scale SkPoint scale; if (!SkDecomposeUpper2x2(m, NULL, &scale, NULL)) { // Decomposition failed, use an approximation. scale.set(SkScalarSqrt(m.getScaleX() * m.getScaleX() + m.getSkewX() * m.getSkewX()), SkScalarSqrt(m.getScaleY() * m.getScaleY() + m.getSkewY() * m.getSkewY())); } SkSize scaledSize = SkSize::Make(scale.x() * fPicture->width(), scale.y() * fPicture->height()); SkISize tileSize = scaledSize.toRound(); if (tileSize.isEmpty()) { return NULL; } // The actual scale, compensating for rounding. SkSize tileScale = SkSize::Make(SkIntToScalar(tileSize.width()) / fPicture->width(), SkIntToScalar(tileSize.height()) / fPicture->height()); SkAutoMutexAcquire ama(fCachedBitmapShaderMutex); if (!fCachedBitmapShader || tileScale != fCachedTileScale) { SkBitmap bm; if (!bm.allocN32Pixels(tileSize.width(), tileSize.height())) { return NULL; } bm.eraseColor(SK_ColorTRANSPARENT); SkCanvas canvas(bm); canvas.scale(tileScale.width(), tileScale.height()); canvas.drawPicture(fPicture); fCachedTileScale = tileScale; SkMatrix shaderMatrix = this->getLocalMatrix(); shaderMatrix.preScale(1 / tileScale.width(), 1 / tileScale.height()); fCachedBitmapShader.reset(CreateBitmapShader(bm, fTmx, fTmy, &shaderMatrix)); } // Increment the ref counter inside the mutex to ensure the returned pointer is still valid. // Otherwise, the pointer may have been overwritten on a different thread before the object's // ref count was incremented. fCachedBitmapShader.get()->ref(); return fCachedBitmapShader; }
void GrCCPathParser::parsePath(const SkMatrix& m, const SkPath& path, SkRect* devBounds, SkRect* devBounds45) { const SkPoint* pts = SkPathPriv::PointData(path); int numPts = path.countPoints(); SkASSERT(numPts + 1 <= fLocalDevPtsBuffer.count()); if (!numPts) { devBounds->setEmpty(); devBounds45->setEmpty(); this->parsePath(path, nullptr); return; } // m45 transforms path points into "45 degree" device space. A bounding box in this space gives // the circumscribing octagon's diagonals. We could use SK_ScalarRoot2Over2, but an orthonormal // transform is not necessary as long as the shader uses the correct inverse. SkMatrix m45; m45.setSinCos(1, 1); m45.preConcat(m); // X,Y,T are two parallel view matrices that accumulate two bounding boxes as they map points: // device-space bounds and "45 degree" device-space bounds (| 1 -1 | * devCoords). // | 1 1 | Sk4f X = Sk4f(m.getScaleX(), m.getSkewY(), m45.getScaleX(), m45.getSkewY()); Sk4f Y = Sk4f(m.getSkewX(), m.getScaleY(), m45.getSkewX(), m45.getScaleY()); Sk4f T = Sk4f(m.getTranslateX(), m.getTranslateY(), m45.getTranslateX(), m45.getTranslateY()); // Map the path's points to device space and accumulate bounding boxes. Sk4f devPt = SkNx_fma(Y, Sk4f(pts[0].y()), T); devPt = SkNx_fma(X, Sk4f(pts[0].x()), devPt); Sk4f topLeft = devPt; Sk4f bottomRight = devPt; // Store all 4 values [dev.x, dev.y, dev45.x, dev45.y]. We are only interested in the first two, // and will overwrite [dev45.x, dev45.y] with the next point. This is why the dst buffer must // be at least one larger than the number of points. devPt.store(&fLocalDevPtsBuffer[0]); for (int i = 1; i < numPts; ++i) { devPt = SkNx_fma(Y, Sk4f(pts[i].y()), T); devPt = SkNx_fma(X, Sk4f(pts[i].x()), devPt); topLeft = Sk4f::Min(topLeft, devPt); bottomRight = Sk4f::Max(bottomRight, devPt); devPt.store(&fLocalDevPtsBuffer[i]); } SkPoint topLeftPts[2], bottomRightPts[2]; topLeft.store(topLeftPts); bottomRight.store(bottomRightPts); devBounds->setLTRB(topLeftPts[0].x(), topLeftPts[0].y(), bottomRightPts[0].x(), bottomRightPts[0].y()); devBounds45->setLTRB(topLeftPts[1].x(), topLeftPts[1].y(), bottomRightPts[1].x(), bottomRightPts[1].y()); this->parsePath(path, fLocalDevPtsBuffer.get()); }
bool SkDrawMatrix::setProperty(int index, SkScriptValue& scriptValue) { SkScalar number = scriptValue.fOperand.fScalar; switch (index) { case SK_PROPERTY(translate): // SkScalar xy[2]; SkASSERT(scriptValue.fType == SkType_Array); SkASSERT(scriptValue.fOperand.fArray->getType() == SkType_Float); SkASSERT(scriptValue.fOperand.fArray->count() == 2); // SkParse::FindScalars(scriptValue.fOperand.fString->c_str(), xy, 2); fMatrix.setTranslateX((*scriptValue.fOperand.fArray)[0].fScalar); fMatrix.setTranslateY((*scriptValue.fOperand.fArray)[1].fScalar); return true; case SK_PROPERTY(perspectX): fMatrix.setPerspX(SkScalarToPersp((number))); break; case SK_PROPERTY(perspectY): fMatrix.setPerspY(SkScalarToPersp((number))); break; case SK_PROPERTY(rotate): { SkMatrix temp; temp.setRotate(number, 0, 0); fMatrix.setScaleX(temp.getScaleX()); fMatrix.setScaleY(temp.getScaleY()); fMatrix.setSkewX(temp.getSkewX()); fMatrix.setSkewY(temp.getSkewY()); } break; case SK_PROPERTY(scale): fMatrix.setScaleX(number); fMatrix.setScaleY(number); break; case SK_PROPERTY(scaleX): fMatrix.setScaleX(number); break; case SK_PROPERTY(scaleY): fMatrix.setScaleY(number); break; case SK_PROPERTY(skewX): fMatrix.setSkewX(number); break; case SK_PROPERTY(skewY): fMatrix.setSkewY(number); break; case SK_PROPERTY(translateX): fMatrix.setTranslateX(number); break; case SK_PROPERTY(translateY): fMatrix.setTranslateY(number); break; default: SkASSERT(0); return false; } fConcat = fMatrix; return true; }
static SkScalar effective_matrix_scale_sqrd(const SkMatrix& mat) { SkPoint v1, v2; v1.fX = mat.getScaleX(); v1.fY = mat.getSkewY(); v2.fX = mat.getSkewX(); v2.fY = mat.getScaleY(); return SkMaxScalar(v1.lengthSqd(), v2.lengthSqd()); }
GrCCPathCache::MaskTransform::MaskTransform(const SkMatrix& m, SkIVector* shift) : fMatrix2x2{m.getScaleX(), m.getSkewX(), m.getSkewY(), m.getScaleY()} { SkASSERT(!m.hasPerspective()); Sk2f translate = Sk2f(m.getTranslateX(), m.getTranslateY()); Sk2f transFloor; #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK // On Android framework we pre-round view matrix translates to integers for better caching. transFloor = translate; #else transFloor = translate.floor(); (translate - transFloor).store(fSubpixelTranslate); #endif shift->set((int)transFloor[0], (int)transFloor[1]); SkASSERT((float)shift->fX == transFloor[0]); // Make sure transFloor had integer values. SkASSERT((float)shift->fY == transFloor[1]); }
static void calculate_translation(bool applyVM, const SkMatrix& newViewMatrix, SkScalar newX, SkScalar newY, const SkMatrix& currentViewMatrix, SkScalar currentX, SkScalar currentY, SkScalar* transX, SkScalar* transY) { if (applyVM) { *transX = newViewMatrix.getTranslateX() + newViewMatrix.getScaleX() * (newX - currentX) + newViewMatrix.getSkewX() * (newY - currentY) - currentViewMatrix.getTranslateX(); *transY = newViewMatrix.getTranslateY() + newViewMatrix.getSkewY() * (newX - currentX) + newViewMatrix.getScaleY() * (newY - currentY) - currentViewMatrix.getTranslateY(); } else { *transX = newX - currentX; *transY = newY - currentY; } }
void SkFlatMatrix::dump() const { const SkMatrix* matrix = (const SkMatrix*) fMatrixData; char pBuffer[DUMP_BUFFER_SIZE]; char* bufferPtr = pBuffer; bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer), "matrix: "); SkScalar scaleX = matrix->getScaleX(); SkMatrix defaultMatrix; defaultMatrix.reset(); if (scaleX != defaultMatrix.getScaleX()) bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer), "scaleX:%g ", SkScalarToFloat(scaleX)); SkScalar scaleY = matrix->getScaleY(); if (scaleY != defaultMatrix.getScaleY()) bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer), "scaleY:%g ", SkScalarToFloat(scaleY)); SkScalar skewX = matrix->getSkewX(); if (skewX != defaultMatrix.getSkewX()) bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer), "skewX:%g ", SkScalarToFloat(skewX)); SkScalar skewY = matrix->getSkewY(); if (skewY != defaultMatrix.getSkewY()) bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer), "skewY:%g ", SkScalarToFloat(skewY)); SkScalar translateX = matrix->getTranslateX(); if (translateX != defaultMatrix.getTranslateX()) bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer), "translateX:%g ", SkScalarToFloat(translateX)); SkScalar translateY = matrix->getTranslateY(); if (translateY != defaultMatrix.getTranslateY()) bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer), "translateY:%g ", SkScalarToFloat(translateY)); SkScalar perspX = matrix->getPerspX(); if (perspX != defaultMatrix.getPerspX()) bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer), "perspX:%g ", SkFractToFloat(perspX)); SkScalar perspY = matrix->getPerspY(); if (perspY != defaultMatrix.getPerspY()) bufferPtr += snprintf(bufferPtr, DUMP_BUFFER_SIZE - (bufferPtr - pBuffer), "perspY:%g ", SkFractToFloat(perspY)); SkDebugf("%s\n", pBuffer); }
// Will the given round rect look good if we use HW derivatives? static bool can_use_hw_derivatives_with_coverage( const GrShaderCaps& shaderCaps, const SkMatrix& viewMatrix, const SkRRect& rrect) { if (!shaderCaps.shaderDerivativeSupport()) { return false; } Sk2f x = Sk2f(viewMatrix.getScaleX(), viewMatrix.getSkewX()); Sk2f y = Sk2f(viewMatrix.getSkewY(), viewMatrix.getScaleY()); Sk2f devScale = (x*x + y*y).sqrt(); switch (rrect.getType()) { case SkRRect::kEmpty_Type: case SkRRect::kRect_Type: return true; case SkRRect::kOval_Type: case SkRRect::kSimple_Type: return can_use_hw_derivatives_with_coverage(devScale, rrect.getSimpleRadii()); case SkRRect::kNinePatch_Type: { Sk2f r0 = Sk2f::Load(SkRRectPriv::GetRadiiArray(rrect)); Sk2f r1 = Sk2f::Load(SkRRectPriv::GetRadiiArray(rrect) + 2); Sk2f minRadii = Sk2f::Min(r0, r1); Sk2f maxRadii = Sk2f::Max(r0, r1); return can_use_hw_derivatives_with_coverage(devScale, Sk2f(minRadii[0], maxRadii[1])) && can_use_hw_derivatives_with_coverage(devScale, Sk2f(maxRadii[0], minRadii[1])); } case SkRRect::kComplex_Type: { for (int i = 0; i < 4; ++i) { auto corner = static_cast<SkRRect::Corner>(i); if (!can_use_hw_derivatives_with_coverage(devScale, rrect.radii(corner))) { return false; } } return true; } } SK_ABORT("Invalid round rect type."); return false; // Add this return to keep GCC happy. }
void SkScalerContextRec::computeMatrices(PreMatrixScale preMatrixScale, SkVector* s, SkMatrix* sA, SkMatrix* GsA, SkMatrix* G_inv, SkMatrix* A_out) { // A is the 'total' matrix. SkMatrix A; this->getSingleMatrix(&A); // The caller may find the 'total' matrix useful when dealing directly with EM sizes. if (A_out) { *A_out = A; } // If the 'total' matrix is singular, set the 'scale' to something finite and zero the matrices. // All underlying ports have issues with zero text size, so use the matricies to zero. // Map the vectors [0,1], [1,0], [1,1] and [1,-1] (the EM) through the 'total' matrix. // If the length of one of these vectors is less than 1/256 then an EM filling square will // never affect any pixels. SkVector diag[4] = { { A.getScaleX() , A.getSkewY() }, { A.getSkewX(), A.getScaleY() }, { A.getScaleX() + A.getSkewX(), A.getScaleY() + A.getSkewY() }, { A.getScaleX() - A.getSkewX(), A.getScaleY() - A.getSkewY() }, }; if (diag[0].lengthSqd() <= SK_ScalarNearlyZero * SK_ScalarNearlyZero || diag[1].lengthSqd() <= SK_ScalarNearlyZero * SK_ScalarNearlyZero || diag[2].lengthSqd() <= SK_ScalarNearlyZero * SK_ScalarNearlyZero || diag[3].lengthSqd() <= SK_ScalarNearlyZero * SK_ScalarNearlyZero) { s->fX = SK_Scalar1; s->fY = SK_Scalar1; sA->setScale(0, 0); if (GsA) { GsA->setScale(0, 0); } if (G_inv) { G_inv->reset(); } return; } // GA is the matrix A with rotation removed. SkMatrix GA; bool skewedOrFlipped = A.getSkewX() || A.getSkewY() || A.getScaleX() < 0 || A.getScaleY() < 0; if (skewedOrFlipped) { // h is where A maps the horizontal baseline. SkPoint h = SkPoint::Make(SK_Scalar1, 0); A.mapPoints(&h, 1); // G is the Givens Matrix for A (rotational matrix where GA[0][1] == 0). SkMatrix G; SkComputeGivensRotation(h, &G); GA = G; GA.preConcat(A); // The 'remainingRotation' is G inverse, which is fairly simple since G is 2x2 rotational. if (G_inv) { G_inv->setAll( G.get(SkMatrix::kMScaleX), -G.get(SkMatrix::kMSkewX), G.get(SkMatrix::kMTransX), -G.get(SkMatrix::kMSkewY), G.get(SkMatrix::kMScaleY), G.get(SkMatrix::kMTransY), G.get(SkMatrix::kMPersp0), G.get(SkMatrix::kMPersp1), G.get(SkMatrix::kMPersp2)); } } else { GA = A; if (G_inv) { G_inv->reset(); } } // At this point, given GA, create s. switch (preMatrixScale) { case kFull_PreMatrixScale: s->fX = SkScalarAbs(GA.get(SkMatrix::kMScaleX)); s->fY = SkScalarAbs(GA.get(SkMatrix::kMScaleY)); break; case kVertical_PreMatrixScale: { SkScalar yScale = SkScalarAbs(GA.get(SkMatrix::kMScaleY)); s->fX = yScale; s->fY = yScale; break; } case kVerticalInteger_PreMatrixScale: { SkScalar realYScale = SkScalarAbs(GA.get(SkMatrix::kMScaleY)); SkScalar intYScale = SkScalarRoundToScalar(realYScale); if (intYScale == 0) { intYScale = SK_Scalar1; } s->fX = intYScale; s->fY = intYScale; break; } } // The 'remaining' matrix sA is the total matrix A without the scale. if (!skewedOrFlipped && ( (kFull_PreMatrixScale == preMatrixScale) || (kVertical_PreMatrixScale == preMatrixScale && A.getScaleX() == A.getScaleY()))) { // If GA == A and kFull_PreMatrixScale, sA is identity. // If GA == A and kVertical_PreMatrixScale and A.scaleX == A.scaleY, sA is identity. sA->reset(); } else if (!skewedOrFlipped && kVertical_PreMatrixScale == preMatrixScale) { // If GA == A and kVertical_PreMatrixScale, sA.scaleY is SK_Scalar1. sA->reset(); sA->setScaleX(A.getScaleX() / s->fY); } else { // TODO: like kVertical_PreMatrixScale, kVerticalInteger_PreMatrixScale with int scales. *sA = A; sA->preScale(SkScalarInvert(s->fX), SkScalarInvert(s->fY)); } // The 'remainingWithoutRotation' matrix GsA is the non-rotational part of A without the scale. if (GsA) { *GsA = GA; // G is rotational so reorders with the scale. GsA->preScale(SkScalarInvert(s->fX), SkScalarInvert(s->fY)); } }
SkScalerContext_CairoFT::SkScalerContext_CairoFT(SkTypeface* typeface, const SkDescriptor* desc) : SkScalerContext_FreeType_Base(typeface, desc) { SkMatrix matrix; fRec.getSingleMatrix(&matrix); cairo_font_face_t* fontFace = static_cast<SkCairoFTTypeface*>(typeface)->getFontFace(); cairo_matrix_t fontMatrix, ctMatrix; cairo_matrix_init(&fontMatrix, matrix.getScaleX(), matrix.getSkewY(), matrix.getSkewX(), matrix.getScaleY(), 0.0, 0.0); cairo_matrix_init_scale(&ctMatrix, 1.0, 1.0); // We need to ensure that the font options match for hinting, as generateMetrics() // uses the fScaledFont which uses these font options cairo_font_options_t *fontOptions = cairo_font_options_create(); FT_Int32 loadFlags = FT_LOAD_DEFAULT; if (SkMask::kBW_Format == fRec.fMaskFormat) { // See http://code.google.com/p/chromium/issues/detail?id=43252#c24 loadFlags = FT_LOAD_TARGET_MONO; if (fRec.getHinting() == SkPaint::kNo_Hinting) { cairo_font_options_set_hint_style(fontOptions, CAIRO_HINT_STYLE_NONE); loadFlags = FT_LOAD_NO_HINTING; } } else { switch (fRec.getHinting()) { case SkPaint::kNo_Hinting: loadFlags = FT_LOAD_NO_HINTING; cairo_font_options_set_hint_style(fontOptions, CAIRO_HINT_STYLE_NONE); break; case SkPaint::kSlight_Hinting: loadFlags = FT_LOAD_TARGET_LIGHT; // This implies FORCE_AUTOHINT cairo_font_options_set_hint_style(fontOptions, CAIRO_HINT_STYLE_SLIGHT); break; case SkPaint::kNormal_Hinting: cairo_font_options_set_hint_style(fontOptions, CAIRO_HINT_STYLE_MEDIUM); if (fRec.fFlags & SkScalerContext::kForceAutohinting_Flag) { loadFlags = FT_LOAD_FORCE_AUTOHINT; } break; case SkPaint::kFull_Hinting: cairo_font_options_set_hint_style(fontOptions, CAIRO_HINT_STYLE_FULL); if (fRec.fFlags & SkScalerContext::kForceAutohinting_Flag) { loadFlags = FT_LOAD_FORCE_AUTOHINT; } if (isLCD(fRec)) { if (SkToBool(fRec.fFlags & SkScalerContext::kLCD_Vertical_Flag)) { loadFlags = FT_LOAD_TARGET_LCD_V; } else { loadFlags = FT_LOAD_TARGET_LCD; } } break; default: SkDebugf("---------- UNKNOWN hinting %d\n", fRec.getHinting()); break; } } fScaledFont = cairo_scaled_font_create(fontFace, &fontMatrix, &ctMatrix, fontOptions); cairo_font_options_destroy(fontOptions); if ((fRec.fFlags & SkScalerContext::kEmbeddedBitmapText_Flag) == 0) { loadFlags |= FT_LOAD_NO_BITMAP; } // Always using FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH to get correct // advances, as fontconfig and cairo do. // See http://code.google.com/p/skia/issues/detail?id=222. loadFlags |= FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH; #ifdef FT_LOAD_COLOR loadFlags |= FT_LOAD_COLOR; #endif fLoadGlyphFlags = loadFlags; }
bool SkScalerContext_CairoFT::computeShapeMatrix(const SkMatrix& m) { // Compute a shape matrix compatible with Cairo's _compute_transform. // Finds major/minor scales and uses them to normalize the transform. double scaleX = m.getScaleX(); double skewX = m.getSkewX(); double skewY = m.getSkewY(); double scaleY = m.getScaleY(); double det = scaleX * scaleY - skewY * skewX; if (!std::isfinite(det)) { fScaleX = fRec.fTextSize * fRec.fPreScaleX; fScaleY = fRec.fTextSize; fHaveShape = false; return false; } double major = det != 0.0 ? hypot(scaleX, skewY) : 0.0; double minor = major != 0.0 ? fabs(det) / major : 0.0; // Limit scales to be above 1pt. major = SkTMax(major, 1.0); minor = SkTMax(minor, 1.0); // If the font is not scalable, then choose the best available size. CairoLockedFTFace faceLock(fScaledFont); FT_Face face = faceLock.getFace(); if (face && !FT_IS_SCALABLE(face)) { double bestDist = DBL_MAX; FT_Int bestSize = -1; for (FT_Int i = 0; i < face->num_fixed_sizes; i++) { // Distance is positive if strike is larger than desired size, // or negative if smaller. If previously a found smaller strike, // then prefer a larger strike. Otherwise, minimize distance. double dist = face->available_sizes[i].y_ppem / 64.0 - minor; if (bestDist < 0 ? dist >= bestDist : fabs(dist) <= bestDist) { bestDist = dist; bestSize = i; } } if (bestSize < 0) { fScaleX = fRec.fTextSize * fRec.fPreScaleX; fScaleY = fRec.fTextSize; fHaveShape = false; return false; } major = face->available_sizes[bestSize].x_ppem / 64.0; minor = face->available_sizes[bestSize].y_ppem / 64.0; fHaveShape = true; } else { fHaveShape = !m.isScaleTranslate(); } fScaleX = SkDoubleToScalar(major); fScaleY = SkDoubleToScalar(minor); if (fHaveShape) { // Normalize the transform and convert to fixed-point. double invScaleX = 65536.0 / major; double invScaleY = 65536.0 / minor; fShapeMatrix.xx = (FT_Fixed)(scaleX * invScaleX); fShapeMatrix.yx = -(FT_Fixed)(skewY * invScaleX); fShapeMatrix.xy = -(FT_Fixed)(skewX * invScaleY); fShapeMatrix.yy = (FT_Fixed)(scaleY * invScaleY); } return true; }
SkScalerContext_CairoFT::SkScalerContext_CairoFT(SkTypeface* typeface, const SkDescriptor* desc, cairo_font_face_t* fontFace, FcPattern* pattern) : SkScalerContext_FreeType_Base(typeface, desc) , fLcdFilter(FT_LCD_FILTER_NONE) { SkMatrix matrix; fRec.getSingleMatrix(&matrix); cairo_matrix_t fontMatrix, ctMatrix; cairo_matrix_init(&fontMatrix, matrix.getScaleX(), matrix.getSkewY(), matrix.getSkewX(), matrix.getScaleY(), 0.0, 0.0); cairo_matrix_init_identity(&ctMatrix); cairo_font_options_t *fontOptions = cairo_font_options_create(); fScaledFont = cairo_scaled_font_create(fontFace, &fontMatrix, &ctMatrix, fontOptions); cairo_font_options_destroy(fontOptions); computeShapeMatrix(matrix); #ifdef CAIRO_HAS_FC_FONT resolvePattern(pattern); #endif FT_Int32 loadFlags = FT_LOAD_DEFAULT; if (SkMask::kBW_Format == fRec.fMaskFormat) { if (fRec.getHinting() == SkPaint::kNo_Hinting) { loadFlags |= FT_LOAD_NO_HINTING; } else { loadFlags = FT_LOAD_TARGET_MONO; } loadFlags |= FT_LOAD_MONOCHROME; } else { switch (fRec.getHinting()) { case SkPaint::kNo_Hinting: loadFlags |= FT_LOAD_NO_HINTING; break; case SkPaint::kSlight_Hinting: loadFlags = FT_LOAD_TARGET_LIGHT; // This implies FORCE_AUTOHINT break; case SkPaint::kNormal_Hinting: if (fRec.fFlags & SkScalerContext::kForceAutohinting_Flag) { loadFlags |= FT_LOAD_FORCE_AUTOHINT; } break; case SkPaint::kFull_Hinting: if (isLCD(fRec)) { if (fRec.fFlags & SkScalerContext::kLCD_Vertical_Flag) { loadFlags = FT_LOAD_TARGET_LCD_V; } else { loadFlags = FT_LOAD_TARGET_LCD; } } if (fRec.fFlags & SkScalerContext::kForceAutohinting_Flag) { loadFlags |= FT_LOAD_FORCE_AUTOHINT; } break; default: SkDebugf("---------- UNKNOWN hinting %d\n", fRec.getHinting()); break; } } // Disable autohinting to disable hinting even for "tricky" fonts. if (!gFontHintingEnabled) { loadFlags |= FT_LOAD_NO_AUTOHINT; } if ((fRec.fFlags & SkScalerContext::kEmbeddedBitmapText_Flag) == 0) { loadFlags |= FT_LOAD_NO_BITMAP; } // Always using FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH to get correct // advances, as fontconfig and cairo do. // See http://code.google.com/p/skia/issues/detail?id=222. loadFlags |= FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH; if (fRec.fFlags & SkScalerContext::kVertical_Flag) { loadFlags |= FT_LOAD_VERTICAL_LAYOUT; } #ifdef FT_LOAD_COLOR loadFlags |= FT_LOAD_COLOR; #endif fLoadGlyphFlags = loadFlags; }
void SkSVGPaint::setSave(SkSVGParser& parser) { SkTDArray<SkString*> clips; SkSVGPaint* walking = parser.fHead; int index; SkMatrix sum; sum.reset(); while (walking != NULL) { for (index = kInitial + 1; index < kTerminal; index++) { SkString* lastAttr = (*walking)[index]; if (lastAttr->size() == 0) continue; if (index == kTransform) { const char* str = lastAttr->c_str(); SkASSERT(strncmp(str, "matrix(", 7) == 0); str += 6; const char* strEnd = strrchr(str, ')'); SkASSERT(strEnd != NULL); SkString mat(str, strEnd - str); SkSVGParser::ConvertToArray(mat); SkScalar values[6]; SkParse::FindScalars(mat.c_str() + 1, values, 6); SkMatrix matrix; matrix.reset(); matrix.setScaleX(values[0]); matrix.setSkewY(values[1]); matrix.setSkewX(values[2]); matrix.setScaleY(values[3]); matrix.setTranslateX(values[4]); matrix.setTranslateY(values[5]); sum.setConcat(matrix, sum); continue; } if ( index == kClipPath) *clips.insert(0) = lastAttr; } walking = walking->fNext; } if ((sum == parser.fLastTransform) == false) { SkMatrix inverse; bool success = parser.fLastTransform.invert(&inverse); SkASSERT(success == true); SkMatrix output; output.setConcat(inverse, sum); parser.fLastTransform = sum; SkString outputStr; outputStr.appendUnichar('['); outputStr.appendScalar(output.getScaleX()); outputStr.appendUnichar(','); outputStr.appendScalar(output.getSkewX()); outputStr.appendUnichar(','); outputStr.appendScalar(output.getTranslateX()); outputStr.appendUnichar(','); outputStr.appendScalar(output.getSkewY()); outputStr.appendUnichar(','); outputStr.appendScalar(output.getScaleY()); outputStr.appendUnichar(','); outputStr.appendScalar(output.getTranslateY()); outputStr.appendUnichar(','); outputStr.appendScalar(output.getPerspX()); outputStr.appendUnichar(','); outputStr.appendScalar(output.getPerspY()); outputStr.append(",1]"); parser._startElement("matrix"); parser._addAttributeLen("matrix", outputStr.c_str(), outputStr.size()); parser._endElement(); } #if 0 // incomplete if (parser.fTransformClips.size() > 0) { // need to reset the clip when the 'g' scope is ended parser._startElement("add"); const char* start = strchr(current->f_clipPath.c_str(), '#') + 1; SkASSERT(start); parser._addAttributeLen("use", start, strlen(start) - 1); parser._endElement(); // clip } #endif }
SkScalerContext_FreeType::SkScalerContext_FreeType(const SkDescriptor* desc) : SkScalerContext(desc), fFTSize(NULL) { SkAutoMutexAcquire ac(gFTMutex); FT_Error err; if (gFTCount == 0) { err = FT_Init_FreeType(&gFTLibrary); // SkDEBUGF(("FT_Init_FreeType returned %d\n", err)); SkASSERT(err == 0); } ++gFTCount; // load the font file fFaceRec = ref_ft_face(fRec.fFontID); fFace = fFaceRec ? fFaceRec->fFace : NULL; // compute our factors from the record SkMatrix m; fRec.getSingleMatrix(&m); #ifdef DUMP_STRIKE_CREATION SkString keyString; SkFontHost::GetDescriptorKeyString(desc, &keyString); printf("========== strike [%g %g %g] [%g %g %g %g] hints %d format %d %s\n", SkScalarToFloat(fRec.fTextSize), SkScalarToFloat(fRec.fPreScaleX), SkScalarToFloat(fRec.fPreSkewX), SkScalarToFloat(fRec.fPost2x2[0][0]), SkScalarToFloat(fRec.fPost2x2[0][1]), SkScalarToFloat(fRec.fPost2x2[1][0]), SkScalarToFloat(fRec.fPost2x2[1][1]), fRec.fHints, fRec.fMaskFormat, keyString.c_str()); #endif // now compute our scale factors SkScalar sx = m.getScaleX(); SkScalar sy = m.getScaleY(); if (m.getSkewX() || m.getSkewY() || sx < 0 || sy < 0) { // sort of give up on hinting sx = SkMaxScalar(SkScalarAbs(sx), SkScalarAbs(m.getSkewX())); sy = SkMaxScalar(SkScalarAbs(m.getSkewY()), SkScalarAbs(sy)); sx = sy = SkScalarAve(sx, sy); SkScalar inv = SkScalarInvert(sx); // flip the skew elements to go from our Y-down system to FreeType's fMatrix22.xx = SkScalarToFixed(SkScalarMul(m.getScaleX(), inv)); fMatrix22.xy = -SkScalarToFixed(SkScalarMul(m.getSkewX(), inv)); fMatrix22.yx = -SkScalarToFixed(SkScalarMul(m.getSkewY(), inv)); fMatrix22.yy = SkScalarToFixed(SkScalarMul(m.getScaleY(), inv)); } else { fMatrix22.xx = fMatrix22.yy = SK_Fixed1; fMatrix22.xy = fMatrix22.yx = 0; } fScaleX = SkScalarToFixed(sx); fScaleY = SkScalarToFixed(sy); // compute the flags we send to Load_Glyph { uint32_t flags = FT_LOAD_DEFAULT; uint32_t render_flags = FT_LOAD_TARGET_NORMAL; // we force autohinting at the moment switch (fRec.fHints) { case kNo_Hints: flags |= FT_LOAD_NO_HINTING; break; case kSubpixel_Hints: flags |= FT_LOAD_FORCE_AUTOHINT; render_flags = FT_LOAD_TARGET_LIGHT; break; case kNormal_Hints: flags |= FT_LOAD_FORCE_AUTOHINT; #ifdef ANDROID /* Switch to light hinting (vertical only) to address some chars that behaved poorly with NORMAL. In the future we could consider making this choice exposed at runtime to the caller. */ render_flags = FT_LOAD_TARGET_LIGHT; #endif break; } if (SkMask::kBW_Format == fRec.fMaskFormat) render_flags = FT_LOAD_TARGET_MONO; else if (SkMask::kLCD_Format == fRec.fMaskFormat) render_flags = FT_LOAD_TARGET_LCD; fLoadGlyphFlags = flags | render_flags; } // now create the FT_Size { FT_Error err; err = FT_New_Size(fFace, &fFTSize); if (err != 0) { SkDEBUGF(("SkScalerContext_FreeType::FT_New_Size(%x): FT_Set_Char_Size(0x%x, 0x%x) returned 0x%x\n", fFaceRec->fFontID, fScaleX, fScaleY, err)); fFace = NULL; return; } err = FT_Activate_Size(fFTSize); if (err != 0) { SkDEBUGF(("SkScalerContext_FreeType::FT_Activate_Size(%x, 0x%x, 0x%x) returned 0x%x\n", fFaceRec->fFontID, fScaleX, fScaleY, err)); fFTSize = NULL; } err = FT_Set_Char_Size( fFace, SkFixedToFDot6(fScaleX), SkFixedToFDot6(fScaleY), 72, 72); if (err != 0) { SkDEBUGF(("SkScalerContext_FreeType::FT_Set_Char_Size(%x, 0x%x, 0x%x) returned 0x%x\n", fFaceRec->fFontID, fScaleX, fScaleY, err)); fFace = NULL; return; } FT_Set_Transform( fFace, &fMatrix22, NULL); } }
bool GrAtlasTextBlob::mustRegenerate(const SkPaint& paint, GrColor color, const SkMaskFilter::BlurRec& blurRec, const SkMatrix& viewMatrix, SkScalar x, SkScalar y) { // If we have LCD text then our canonical color will be set to transparent, in this case we have // to regenerate the blob on any color change // We use the grPaint to get any color filter effects if (fKey.fCanonicalColor == SK_ColorTRANSPARENT && fPaintColor != color) { return true; } if (fInitialViewMatrix.hasPerspective() != viewMatrix.hasPerspective()) { return true; } if (fInitialViewMatrix.hasPerspective() && !fInitialViewMatrix.cheapEqualTo(viewMatrix)) { return true; } // We only cache one masked version if (fKey.fHasBlur && (fBlurRec.fSigma != blurRec.fSigma || fBlurRec.fStyle != blurRec.fStyle || fBlurRec.fQuality != blurRec.fQuality)) { return true; } // Similarly, we only cache one version for each style if (fKey.fStyle != SkPaint::kFill_Style && (fStrokeInfo.fFrameWidth != paint.getStrokeWidth() || fStrokeInfo.fMiterLimit != paint.getStrokeMiter() || fStrokeInfo.fJoin != paint.getStrokeJoin())) { return true; } // Mixed blobs must be regenerated. We could probably figure out a way to do integer scrolls // for mixed blobs if this becomes an issue. if (this->hasBitmap() && this->hasDistanceField()) { // Identical viewmatrices and we can reuse in all cases if (fInitialViewMatrix.cheapEqualTo(viewMatrix) && x == fInitialX && y == fInitialY) { return false; } return true; } if (this->hasBitmap()) { if (fInitialViewMatrix.getScaleX() != viewMatrix.getScaleX() || fInitialViewMatrix.getScaleY() != viewMatrix.getScaleY() || fInitialViewMatrix.getSkewX() != viewMatrix.getSkewX() || fInitialViewMatrix.getSkewY() != viewMatrix.getSkewY()) { return true; } // We can update the positions in the cachedtextblobs without regenerating the whole blob, // but only for integer translations. // This cool bit of math will determine the necessary translation to apply to the already // generated vertex coordinates to move them to the correct position SkScalar transX = viewMatrix.getTranslateX() + viewMatrix.getScaleX() * (x - fInitialX) + viewMatrix.getSkewX() * (y - fInitialY) - fInitialViewMatrix.getTranslateX(); SkScalar transY = viewMatrix.getTranslateY() + viewMatrix.getSkewY() * (x - fInitialX) + viewMatrix.getScaleY() * (y - fInitialY) - fInitialViewMatrix.getTranslateY(); if (!SkScalarIsInt(transX) || !SkScalarIsInt(transY)) { return true; } } else if (this->hasDistanceField()) { // A scale outside of [blob.fMaxMinScale, blob.fMinMaxScale] would result in a different // distance field being generated, so we have to regenerate in those cases SkScalar newMaxScale = viewMatrix.getMaxScale(); SkScalar oldMaxScale = fInitialViewMatrix.getMaxScale(); SkScalar scaleAdjust = newMaxScale / oldMaxScale; if (scaleAdjust < fMaxMinScale || scaleAdjust > fMinMaxScale) { return true; } } // It is possible that a blob has neither distanceField nor bitmaptext. This is in the case // when all of the runs inside the blob are drawn as paths. In this case, we always regenerate // the blob anyways at flush time, so no need to regenerate explicitly return false; }
SkScalerContext_FreeType::SkScalerContext_FreeType(const SkDescriptor* desc) : SkScalerContext(desc) { SkAutoMutexAcquire ac(gFTMutex); if (gFTCount == 0) { if (!InitFreetype()) { sk_throw(); } } ++gFTCount; // load the font file fFTSize = NULL; fFace = NULL; fFaceRec = ref_ft_face(fRec.fFontID); if (NULL == fFaceRec) { return; } fFace = fFaceRec->fFace; // compute our factors from the record SkMatrix m; fRec.getSingleMatrix(&m); #ifdef DUMP_STRIKE_CREATION SkString keyString; SkFontHost::GetDescriptorKeyString(desc, &keyString); printf("========== strike [%g %g %g] [%g %g %g %g] hints %d format %d %s\n", SkScalarToFloat(fRec.fTextSize), SkScalarToFloat(fRec.fPreScaleX), SkScalarToFloat(fRec.fPreSkewX), SkScalarToFloat(fRec.fPost2x2[0][0]), SkScalarToFloat(fRec.fPost2x2[0][1]), SkScalarToFloat(fRec.fPost2x2[1][0]), SkScalarToFloat(fRec.fPost2x2[1][1]), fRec.getHinting(), fRec.fMaskFormat, keyString.c_str()); #endif // now compute our scale factors SkScalar sx = m.getScaleX(); SkScalar sy = m.getScaleY(); if (m.getSkewX() || m.getSkewY() || sx < 0 || sy < 0) { // sort of give up on hinting sx = SkMaxScalar(SkScalarAbs(sx), SkScalarAbs(m.getSkewX())); sy = SkMaxScalar(SkScalarAbs(m.getSkewY()), SkScalarAbs(sy)); sx = sy = SkScalarAve(sx, sy); SkScalar inv = SkScalarInvert(sx); // flip the skew elements to go from our Y-down system to FreeType's fMatrix22.xx = SkScalarToFixed(SkScalarMul(m.getScaleX(), inv)); fMatrix22.xy = -SkScalarToFixed(SkScalarMul(m.getSkewX(), inv)); fMatrix22.yx = -SkScalarToFixed(SkScalarMul(m.getSkewY(), inv)); fMatrix22.yy = SkScalarToFixed(SkScalarMul(m.getScaleY(), inv)); } else { fMatrix22.xx = fMatrix22.yy = SK_Fixed1; fMatrix22.xy = fMatrix22.yx = 0; } fScaleX = SkScalarToFixed(sx); fScaleY = SkScalarToFixed(sy); // compute the flags we send to Load_Glyph { FT_Int32 loadFlags = FT_LOAD_DEFAULT; if (SkMask::kBW_Format == fRec.fMaskFormat) { // See http://code.google.com/p/chromium/issues/detail?id=43252#c24 loadFlags = FT_LOAD_TARGET_MONO; if (fRec.getHinting() == SkPaint::kNo_Hinting) loadFlags = FT_LOAD_NO_HINTING; } else { switch (fRec.getHinting()) { case SkPaint::kNo_Hinting: loadFlags = FT_LOAD_NO_HINTING; break; case SkPaint::kSlight_Hinting: loadFlags = FT_LOAD_TARGET_LIGHT; // This implies FORCE_AUTOHINT break; case SkPaint::kNormal_Hinting: if (fRec.fFlags & SkScalerContext::kAutohinting_Flag) loadFlags = FT_LOAD_FORCE_AUTOHINT; else loadFlags = FT_LOAD_NO_AUTOHINT; break; case SkPaint::kFull_Hinting: if (fRec.fFlags & SkScalerContext::kAutohinting_Flag) { loadFlags = FT_LOAD_FORCE_AUTOHINT; break; } loadFlags = FT_LOAD_TARGET_NORMAL; if (SkMask::kHorizontalLCD_Format == fRec.fMaskFormat || SkMask::kLCD16_Format == fRec.fMaskFormat) { loadFlags = FT_LOAD_TARGET_LCD; } else if (SkMask::kVerticalLCD_Format == fRec.fMaskFormat) { loadFlags = FT_LOAD_TARGET_LCD_V; } break; default: SkDebugf("---------- UNKNOWN hinting %d\n", fRec.getHinting()); break; } } if ((fRec.fFlags & SkScalerContext::kEmbeddedBitmapText_Flag) == 0) loadFlags |= FT_LOAD_NO_BITMAP; // Always using FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH to get correct // advances, as fontconfig and cairo do. // See http://code.google.com/p/skia/issues/detail?id=222. loadFlags |= FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH; fLoadGlyphFlags = loadFlags; } // now create the FT_Size { FT_Error err; err = FT_New_Size(fFace, &fFTSize); if (err != 0) { SkDEBUGF(("SkScalerContext_FreeType::FT_New_Size(%x): FT_Set_Char_Size(0x%x, 0x%x) returned 0x%x\n", fFaceRec->fFontID, fScaleX, fScaleY, err)); fFace = NULL; return; } err = FT_Activate_Size(fFTSize); if (err != 0) { SkDEBUGF(("SkScalerContext_FreeType::FT_Activate_Size(%x, 0x%x, 0x%x) returned 0x%x\n", fFaceRec->fFontID, fScaleX, fScaleY, err)); fFTSize = NULL; } err = FT_Set_Char_Size( fFace, SkFixedToFDot6(fScaleX), SkFixedToFDot6(fScaleY), 72, 72); if (err != 0) { SkDEBUGF(("SkScalerContext_FreeType::FT_Set_Char_Size(%x, 0x%x, 0x%x) returned 0x%x\n", fFaceRec->fFontID, fScaleX, fScaleY, err)); fFace = NULL; return; } FT_Set_Transform( fFace, &fMatrix22, NULL); } }
std::unique_ptr<GrFillRRectOp> GrFillRRectOp::Make( GrRecordingContext* ctx, GrAAType aaType, const SkMatrix& viewMatrix, const SkRRect& rrect, const GrCaps& caps, GrPaint&& paint) { if (!caps.instanceAttribSupport()) { return nullptr; } Flags flags = Flags::kNone; if (GrAAType::kCoverage == aaType) { // TODO: Support perspective in a follow-on CL. This shouldn't be difficult, since we // already use HW derivatives. The only trick will be adjusting the AA outset to account for // perspective. (i.e., outset = 0.5 * z.) if (viewMatrix.hasPerspective()) { return nullptr; } if (can_use_hw_derivatives_with_coverage(*caps.shaderCaps(), viewMatrix, rrect)) { // HW derivatives (more specifically, fwidth()) are consistently faster on all platforms // in coverage mode. We use them as long as the approximation will be accurate enough. flags |= Flags::kUseHWDerivatives; } } else { if (GrAAType::kMSAA == aaType) { if (!caps.sampleLocationsSupport() || !caps.shaderCaps()->sampleVariablesSupport()) { return nullptr; } } if (viewMatrix.hasPerspective()) { // HW derivatives are consistently slower on all platforms in sample mask mode. We // therefore only use them when there is perspective, since then we can't interpolate // the symbolic screen-space gradient. flags |= Flags::kUseHWDerivatives | Flags::kHasPerspective; } } // Produce a matrix that draws the round rect from normalized [-1, -1, +1, +1] space. float l = rrect.rect().left(), r = rrect.rect().right(), t = rrect.rect().top(), b = rrect.rect().bottom(); SkMatrix m; // Unmap the normalized rect [-1, -1, +1, +1] back to [l, t, r, b]. m.setScaleTranslate((r - l)/2, (b - t)/2, (l + r)/2, (t + b)/2); // Map to device space. m.postConcat(viewMatrix); SkRect devBounds; if (!(flags & Flags::kHasPerspective)) { // Since m is an affine matrix that maps the rect [-1, -1, +1, +1] into the shape's // device-space quad, it's quite simple to find the bounding rectangle: devBounds = SkRect::MakeXYWH(m.getTranslateX(), m.getTranslateY(), 0, 0); devBounds.outset(SkScalarAbs(m.getScaleX()) + SkScalarAbs(m.getSkewX()), SkScalarAbs(m.getSkewY()) + SkScalarAbs(m.getScaleY())); } else { viewMatrix.mapRect(&devBounds, rrect.rect()); } if (GrAAType::kMSAA == aaType && caps.preferTrianglesOverSampleMask()) { // We are on a platform that prefers fine triangles instead of using the sample mask. See if // the round rect is large enough that it will be faster for us to send it off to the // default path renderer instead. The 200x200 threshold was arrived at using the // "shapes_rrect" benchmark on an ARM Galaxy S9. if (devBounds.height() * devBounds.width() > 200 * 200) { return nullptr; } } GrOpMemoryPool* pool = ctx->priv().opMemoryPool(); return pool->allocate<GrFillRRectOp>(aaType, rrect, flags, m, std::move(paint), devBounds); }