/** * Determine if the matrix can be treated as integral-only-translate, * for the purpose of filtering. */ static bool just_trans_integral(const SkMatrix& m) { static constexpr SkScalar tol = SK_Scalar1 / 256; return m.getType() <= SkMatrix::kTranslate_Mask && SkScalarNearlyEqual(m.getTranslateX(), SkScalarRoundToScalar(m.getTranslateX()), tol) && SkScalarNearlyEqual(m.getTranslateY(), SkScalarRoundToScalar(m.getTranslateY()), tol); }
// Test out questionable-parameter handling static void test_round_rect_iffy_parameters(skiatest::Reporter* reporter) { // When the radii exceed the base rect they are proportionally scaled down // to fit SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight); SkPoint radii[4] = { { 50, 100 }, { 100, 50 }, { 50, 100 }, { 100, 50 } }; SkRRect rr1; rr1.setRectRadii(rect, radii); REPORTER_ASSERT(reporter, SkRRect::kComplex_Type == rr1.type()); const SkPoint& p = rr1.radii(SkRRect::kUpperLeft_Corner); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(p.fX, 33.33333f)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(p.fY, 66.66666f)); // Negative radii should be capped at zero SkRRect rr2; rr2.setRectXY(rect, -10, -20); REPORTER_ASSERT(reporter, SkRRect::kRect_Type == rr2.type()); const SkPoint& p2 = rr2.radii(SkRRect::kUpperLeft_Corner); REPORTER_ASSERT(reporter, 0.0f == p2.fX); REPORTER_ASSERT(reporter, 0.0f == p2.fY); }
DEF_TEST(contour_measure, reporter) { SkPath path; path.addCircle(0, 0, 100); path.addCircle(0, 0, 10); SkContourMeasureIter fact(path, false); path.reset(); // we should not need the path avert we created the factory auto cm0 = fact.next(); auto cm1 = fact.next(); REPORTER_ASSERT(reporter, cm0->isClosed()); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(cm0->length(), 200 * SK_ScalarPI, 1.5f)); test_90_degrees(cm0, 100, reporter); REPORTER_ASSERT(reporter, cm1->isClosed()); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(cm1->length(), 20 * SK_ScalarPI, 0.5f)); test_90_degrees(cm1, 10, reporter); auto cm2 = fact.next(); REPORTER_ASSERT(reporter, !cm2); test_empty_contours(reporter); test_MLM_contours(reporter); }
/* * High quality is implemented by performing up-right scale-only filtering and then * using bilerp for any remaining transformations. */ bool SkDefaultBitmapControllerState::processHQRequest(const SkBitmap& origBitmap) { if (fQuality != kHigh_SkFilterQuality) { return false; } // Our default return state is to downgrade the request to Medium, w/ or w/o setting fBitmap // to a valid bitmap. If we succeed, we will set this to Low instead. fQuality = kMedium_SkFilterQuality; if (kN32_SkColorType != origBitmap.colorType() || !cache_size_okay(origBitmap, fInvMatrix) || fInvMatrix.hasPerspective()) { return false; // can't handle the reqeust } SkScalar invScaleX = fInvMatrix.getScaleX(); SkScalar invScaleY = fInvMatrix.getScaleY(); if (fInvMatrix.getType() & SkMatrix::kAffine_Mask) { SkSize scale; if (!fInvMatrix.decomposeScale(&scale)) { return false; } invScaleX = scale.width(); invScaleY = scale.height(); } if (SkScalarNearlyEqual(invScaleX, 1) && SkScalarNearlyEqual(invScaleY, 1)) { return false; // no need for HQ } SkScalar trueDestWidth = origBitmap.width() / invScaleX; SkScalar trueDestHeight = origBitmap.height() / invScaleY; SkScalar roundedDestWidth = SkScalarRoundToScalar(trueDestWidth); SkScalar roundedDestHeight = SkScalarRoundToScalar(trueDestHeight); if (!SkBitmapCache::Find(origBitmap, roundedDestWidth, roundedDestHeight, &fResultBitmap)) { SkAutoPixmapUnlock src; if (!origBitmap.requestLock(&src)) { return false; } if (!SkBitmapScaler::Resize(&fResultBitmap, src.pixmap(), SkBitmapScaler::RESIZE_BEST, roundedDestWidth, roundedDestHeight, SkResourceCache::GetAllocator())) { return false; // we failed to create fScaledBitmap } SkASSERT(fResultBitmap.getPixels()); fResultBitmap.setImmutable(); SkBitmapCache::Add(origBitmap, roundedDestWidth, roundedDestHeight, fResultBitmap); } SkASSERT(fResultBitmap.getPixels()); fInvMatrix.postScale(roundedDestWidth / origBitmap.width(), roundedDestHeight / origBitmap.height()); fQuality = kLow_SkFilterQuality; return true; }
static void check_pairs(skiatest::Reporter* reporter, int index, SkScalar t, const char name[], SkScalar x0, SkScalar y0, SkScalar x1, SkScalar y1) { bool eq = SkScalarNearlyEqual(x0, x1) && SkScalarNearlyEqual(y0, y1); if (!eq) { SkDebugf("%s [%d %g] p0 [%10.8f %10.8f] p1 [%10.8f %10.8f]\n", name, index, t, x0, y0, x1, y1); REPORTER_ASSERT(reporter, eq); } }
static SkXfermode* Create(SkScalar k1, SkScalar k2, SkScalar k3, SkScalar k4, bool enforcePMColor) { if (SkScalarNearlyZero(k1) && SkScalarNearlyEqual(k2, SK_Scalar1) && SkScalarNearlyZero(k3) && SkScalarNearlyZero(k4)) { return SkXfermode::Create(SkXfermode::kSrc_Mode); } else if (SkScalarNearlyZero(k1) && SkScalarNearlyZero(k2) && SkScalarNearlyEqual(k3, SK_Scalar1) && SkScalarNearlyZero(k4)) { return SkXfermode::Create(SkXfermode::kDst_Mode); } return new SkArithmeticMode_scalar(k1, k2, k3, k4, enforcePMColor); }
static void test_90_degrees(sk_sp<SkContourMeasure> cm, SkScalar radius, skiatest::Reporter* reporter) { SkPoint pos; SkVector tan; SkScalar distance = cm->length() / 4; bool success = cm->getPosTan(distance, &pos, &tan); REPORTER_ASSERT(reporter, success); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(pos.fX, 0)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(pos.fY, radius)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(tan.fX, -1)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(tan.fY, 0)); }
void SkNormalMapSourceImpl::Provider::fillScanLine(int x, int y, SkPoint3 output[], int count) const { SkPMColor tmpNormalColors[BUFFER_MAX]; do { int n = SkTMin(count, BUFFER_MAX); fMapContext->shadeSpan(x, y, tmpNormalColors, n); for (int i = 0; i < n; i++) { SkPoint3 tempNorm; tempNorm.set(SkIntToScalar(SkGetPackedR32(tmpNormalColors[i])) - 127.0f, SkIntToScalar(SkGetPackedG32(tmpNormalColors[i])) - 127.0f, SkIntToScalar(SkGetPackedB32(tmpNormalColors[i])) - 127.0f); tempNorm.normalize(); if (!SkScalarNearlyEqual(SkScalarAbs(tempNorm.fZ), 1.0f)) { SkVector transformed = fSource.fInvCTM.mapVector(tempNorm.fX, tempNorm.fY); // Normalizing the transformed X and Y, while keeping constant both Z and the // vector's angle in the XY plane. This maintains the "slope" for the surface while // appropriately rotating the normal for any anisotropic scaling that occurs. // Here, we call scaling factor the number that must divide the transformed X and Y // so that the normal's length remains equal to 1. SkScalar scalingFactorSquared = (SkScalarSquare(transformed.fX) + SkScalarSquare(transformed.fY)) / (1.0f - SkScalarSquare(tempNorm.fZ)); SkScalar invScalingFactor = SkScalarInvert(SkScalarSqrt(scalingFactorSquared)); output[i].fX = transformed.fX * invScalingFactor; output[i].fY = transformed.fY * invScalingFactor; output[i].fZ = tempNorm.fZ; } else { output[i] = {0.0f, 0.0f, tempNorm.fZ}; output[i].normalize(); } SkASSERT(SkScalarNearlyEqual(output[i].length(), 1.0f)); } output += n; x += n; count -= n; } while (count > 0); }
static inline void norm_to_rgb(SkBitmap* bm, int x, int y, const SkVector3& norm) { SkASSERT(SkScalarNearlyEqual(norm.length(), 1.0f)); unsigned char r = static_cast<unsigned char>((0.5f * norm.fX + 0.5f) * 255); unsigned char g = static_cast<unsigned char>((-0.5f * norm.fY + 0.5f) * 255); unsigned char b = static_cast<unsigned char>((0.5f * norm.fZ + 0.5f) * 255); *bm->getAddr32(x, y) = SkPackARGB32(0xFF, r, g, b); }
void Path::addEllipse(const FloatPoint& p, float radiusX, float radiusY, float startAngle, float endAngle, bool anticlockwise) { ASSERT(ellipseIsRenderable(startAngle, endAngle)); ASSERT(startAngle >= 0 && startAngle < twoPiFloat); ASSERT((anticlockwise && (startAngle - endAngle) >= 0) || (!anticlockwise && (endAngle - startAngle) >= 0)); SkScalar cx = WebCoreFloatToSkScalar(p.x()); SkScalar cy = WebCoreFloatToSkScalar(p.y()); SkScalar radiusXScalar = WebCoreFloatToSkScalar(radiusX); SkScalar radiusYScalar = WebCoreFloatToSkScalar(radiusY); SkRect oval; oval.set(cx - radiusXScalar, cy - radiusYScalar, cx + radiusXScalar, cy + radiusYScalar); float sweep = endAngle - startAngle; SkScalar startDegrees = WebCoreFloatToSkScalar(startAngle * 180 / piFloat); SkScalar sweepDegrees = WebCoreFloatToSkScalar(sweep * 180 / piFloat); SkScalar s360 = SkIntToScalar(360); // We can't use SkPath::addOval(), because addOval() makes a new sub-path. // addOval() calls moveTo() and close() internally. // Use s180, not s360, because SkPath::arcTo(oval, angle, s360, false) draws // nothing. SkScalar s180 = SkIntToScalar(180); if (SkScalarNearlyEqual(sweepDegrees, s360)) { // SkPath::arcTo can't handle the sweepAngle that is equal to or greater // than 2Pi. m_path.arcTo(oval, startDegrees, s180, false); m_path.arcTo(oval, startDegrees + s180, s180, false); return; } if (SkScalarNearlyEqual(sweepDegrees, -s360)) { m_path.arcTo(oval, startDegrees, -s180, false); m_path.arcTo(oval, startDegrees - s180, -s180, false); return; } m_path.arcTo(oval, startDegrees, sweepDegrees, false); }
static bool is_rectilinear (SkVector4& p1, SkVector4& p2, SkVector4& p3, SkVector4& p4) { return (SkScalarNearlyEqual(p1.fData[0], p2.fData[0]) && SkScalarNearlyEqual(p2.fData[1], p3.fData[1]) && SkScalarNearlyEqual(p3.fData[0], p4.fData[0]) && SkScalarNearlyEqual(p4.fData[1], p1.fData[1])) || (SkScalarNearlyEqual(p1.fData[1], p2.fData[1]) && SkScalarNearlyEqual(p2.fData[0], p3.fData[0]) && SkScalarNearlyEqual(p3.fData[1], p4.fData[1]) && SkScalarNearlyEqual(p4.fData[0], p1.fData[0])); }
inline void GrDistanceFieldTextContext::init(GrRenderTarget* rt, const GrClip& clip, const GrPaint& paint, const SkPaint& skPaint, const SkIRect& regionClipBounds) { GrTextContext::init(rt, clip, paint, skPaint, regionClipBounds); fStrike = NULL; const SkMatrix& ctm = fViewMatrix; // getMaxScale doesn't support perspective, so neither do we at the moment SkASSERT(!ctm.hasPerspective()); SkScalar maxScale = ctm.getMaxScale(); SkScalar textSize = fSkPaint.getTextSize(); SkScalar scaledTextSize = textSize; // if we have non-unity scale, we need to choose our base text size // based on the SkPaint's text size multiplied by the max scale factor // TODO: do we need to do this if we're scaling down (i.e. maxScale < 1)? if (maxScale > 0 && !SkScalarNearlyEqual(maxScale, SK_Scalar1)) { scaledTextSize *= maxScale; } fVertices = NULL; fCurrVertex = 0; fAllocVertexCount = 0; fTotalVertexCount = 0; if (scaledTextSize <= kSmallDFFontLimit) { fTextRatio = textSize / kSmallDFFontSize; fSkPaint.setTextSize(SkIntToScalar(kSmallDFFontSize)); #if DEBUG_TEXT_SIZE fSkPaint.setColor(SkColorSetARGB(0xFF, 0x00, 0x00, 0x7F)); fPaint.setColor(GrColorPackRGBA(0x00, 0x00, 0x7F, 0xFF)); #endif } else if (scaledTextSize <= kMediumDFFontLimit) { fTextRatio = textSize / kMediumDFFontSize; fSkPaint.setTextSize(SkIntToScalar(kMediumDFFontSize)); #if DEBUG_TEXT_SIZE fSkPaint.setColor(SkColorSetARGB(0xFF, 0x00, 0x3F, 0x00)); fPaint.setColor(GrColorPackRGBA(0x00, 0x3F, 0x00, 0xFF)); #endif } else { fTextRatio = textSize / kLargeDFFontSize; fSkPaint.setTextSize(SkIntToScalar(kLargeDFFontSize)); #if DEBUG_TEXT_SIZE fSkPaint.setColor(SkColorSetARGB(0xFF, 0x7F, 0x00, 0x00)); fPaint.setColor(GrColorPackRGBA(0x7F, 0x00, 0x00, 0xFF)); #endif } fUseLCDText = fSkPaint.isLCDRenderText(); fSkPaint.setLCDRenderText(false); fSkPaint.setAutohinted(false); fSkPaint.setHinting(SkPaint::kNormal_Hinting); fSkPaint.setSubpixelText(true); }
void GrTextUtils::InitDistanceFieldPaint(GrAtlasTextBlob* blob, SkPaint* skPaint, SkScalar* textRatio, const SkMatrix& viewMatrix) { // getMaxScale doesn't support perspective, so neither do we at the moment SkASSERT(!viewMatrix.hasPerspective()); SkScalar maxScale = viewMatrix.getMaxScale(); SkScalar textSize = skPaint->getTextSize(); SkScalar scaledTextSize = textSize; // if we have non-unity scale, we need to choose our base text size // based on the SkPaint's text size multiplied by the max scale factor // TODO: do we need to do this if we're scaling down (i.e. maxScale < 1)? if (maxScale > 0 && !SkScalarNearlyEqual(maxScale, SK_Scalar1)) { scaledTextSize *= maxScale; } // We have three sizes of distance field text, and within each size 'bucket' there is a floor // and ceiling. A scale outside of this range would require regenerating the distance fields SkScalar dfMaskScaleFloor; SkScalar dfMaskScaleCeil; if (scaledTextSize <= kSmallDFFontLimit) { dfMaskScaleFloor = kMinDFFontSize; dfMaskScaleCeil = kSmallDFFontLimit; *textRatio = textSize / kSmallDFFontSize; skPaint->setTextSize(SkIntToScalar(kSmallDFFontSize)); } else if (scaledTextSize <= kMediumDFFontLimit) { dfMaskScaleFloor = kSmallDFFontLimit; dfMaskScaleCeil = kMediumDFFontLimit; *textRatio = textSize / kMediumDFFontSize; skPaint->setTextSize(SkIntToScalar(kMediumDFFontSize)); } else { dfMaskScaleFloor = kMediumDFFontLimit; dfMaskScaleCeil = kLargeDFFontLimit; *textRatio = textSize / kLargeDFFontSize; skPaint->setTextSize(SkIntToScalar(kLargeDFFontSize)); } // Because there can be multiple runs in the blob, we want the overall maxMinScale, and // minMaxScale to make regeneration decisions. Specifically, we want the maximum minimum scale // we can tolerate before we'd drop to a lower mip size, and the minimum maximum scale we can // tolerate before we'd have to move to a large mip size. When we actually test these values // we look at the delta in scale between the new viewmatrix and the old viewmatrix, and test // against these values to decide if we can reuse or not(ie, will a given scale change our mip // level) SkASSERT(dfMaskScaleFloor <= scaledTextSize && scaledTextSize <= dfMaskScaleCeil); blob->setMinAndMaxScale(dfMaskScaleFloor / scaledTextSize, dfMaskScaleCeil / scaledTextSize); skPaint->setLCDRenderText(false); skPaint->setAutohinted(false); skPaint->setHinting(SkPaint::kNormal_Hinting); skPaint->setSubpixelText(true); }
// Test out the case where an oval already off in space is translated/scaled // further off into space - yielding numerical issues when the rect & radii // are transformed separatly // BUG=skia:2696 static void test_issue_2696(skiatest::Reporter* reporter) { SkRRect rrect; SkRect r = { 28443.8594f, 53.1428604f, 28446.7148f, 56.0000038f }; rrect.setOval(r); SkMatrix xform; xform.setAll(2.44f, 0.0f, 485411.7f, 0.0f, 2.44f, -438.7f, 0.0f, 0.0f, 1.0f); SkRRect dst; bool success = rrect.transform(xform, &dst); REPORTER_ASSERT(reporter, success); SkScalar halfWidth = SkScalarHalf(dst.width()); SkScalar halfHeight = SkScalarHalf(dst.height()); for (int i = 0; i < 4; ++i) { REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst.radii((SkRRect::Corner)i).fX, halfWidth)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst.radii((SkRRect::Corner)i).fY, halfHeight)); } }
GrFragmentProcessor* GrOvalEffect::Create(GrPrimitiveEdgeType edgeType, const SkRect& oval) { if (kHairlineAA_GrProcessorEdgeType == edgeType) { return NULL; } SkScalar w = oval.width(); SkScalar h = oval.height(); if (SkScalarNearlyEqual(w, h)) { w /= 2; return CircleEffect::Create(edgeType, SkPoint::Make(oval.fLeft + w, oval.fTop + w), w); } else { w /= 2; h /= 2; return EllipseEffect::Create(edgeType, SkPoint::Make(oval.fLeft + w, oval.fTop + h), w, h); } return NULL; }
SkScalar scaled_text_size(const SkScalar textSize, const SkMatrix& viewMatrix) { SkScalar scaledTextSize = textSize; if (viewMatrix.hasPerspective()) { // for perspective, we simply force to the medium size // TODO: compute a size based on approximate screen area scaledTextSize = kMediumDFFontLimit; } else { SkScalar maxScale = viewMatrix.getMaxScale(); // if we have non-unity scale, we need to choose our base text size // based on the SkPaint's text size multiplied by the max scale factor // TODO: do we need to do this if we're scaling down (i.e. maxScale < 1)? if (maxScale > 0 && !SkScalarNearlyEqual(maxScale, SK_Scalar1)) { scaledTextSize *= maxScale; } } return scaledTextSize; }
void GrAAConvexTessellator::computeBisectors() { fBisectors.setCount(fNorms.count()); int prev = fBisectors.count() - 1; for (int cur = 0; cur < fBisectors.count(); prev = cur, ++cur) { fBisectors[cur] = fNorms[cur] + fNorms[prev]; if (!fBisectors[cur].normalize()) { SkASSERT(SkPoint::kLeft_Side == fSide || SkPoint::kRight_Side == fSide); fBisectors[cur].setOrthog(fNorms[cur], (SkPoint::Side)-fSide); SkVector other; other.setOrthog(fNorms[prev], fSide); fBisectors[cur] += other; SkAssertResult(fBisectors[cur].normalize()); } else { fBisectors[cur].negate(); // make the bisector face in } SkASSERT(SkScalarNearlyEqual(1.0f, fBisectors[cur].length())); } }
// Offset line segment p0-p1 'd0' and 'd1' units in the direction specified by 'side' bool SkOffsetSegment(const SkPoint& p0, const SkPoint& p1, SkScalar d0, SkScalar d1, int side, SkPoint* offset0, SkPoint* offset1) { SkASSERT(side == -1 || side == 1); SkVector perp = SkVector::Make(p0.fY - p1.fY, p1.fX - p0.fX); if (SkScalarNearlyEqual(d0, d1)) { // if distances are equal, can just outset by the perpendicular perp.setLength(d0*side); *offset0 = p0 + perp; *offset1 = p1 + perp; } else { // Otherwise we need to compute the outer tangent. // See: http://www.ambrsoft.com/TrigoCalc/Circles2/Circles2Tangent_.htm if (d0 < d1) { side = -side; } SkScalar dD = d0 - d1; // if one circle is inside another, we can't compute an offset if (dD*dD >= p0.distanceToSqd(p1)) { return false; } SkPoint outerTangentIntersect = SkPoint::Make((p1.fX*d0 - p0.fX*d1) / dD, (p1.fY*d0 - p0.fY*d1) / dD); SkScalar d0sq = d0*d0; SkVector dP = outerTangentIntersect - p0; SkScalar dPlenSq = dP.lengthSqd(); SkScalar discrim = SkScalarSqrt(dPlenSq - d0sq); offset0->fX = p0.fX + (d0sq*dP.fX - side*d0*dP.fY*discrim) / dPlenSq; offset0->fY = p0.fY + (d0sq*dP.fY + side*d0*dP.fX*discrim) / dPlenSq; SkScalar d1sq = d1*d1; dP = outerTangentIntersect - p1; dPlenSq = dP.lengthSqd(); discrim = SkScalarSqrt(dPlenSq - d1sq); offset1->fX = p1.fX + (d1sq*dP.fX - side*d1*dP.fY*discrim) / dPlenSq; offset1->fY = p1.fY + (d1sq*dP.fY + side*d1*dP.fX*discrim) / dPlenSq; } return true; }
static void test_matrix_homogeneous(skiatest::Reporter* reporter) { SkMatrix mat; const float kRotation0 = 15.5f; const float kRotation1 = -50.f; const float kScale0 = 5000.f; const int kTripleCount = 1000; const int kMatrixCount = 1000; SkRandom rand; SkScalar randTriples[3*kTripleCount]; for (int i = 0; i < 3*kTripleCount; ++i) { randTriples[i] = rand.nextRangeF(-3000.f, 3000.f); } SkMatrix mats[kMatrixCount]; for (int i = 0; i < kMatrixCount; ++i) { for (int j = 0; j < 9; ++j) { mats[i].set(j, rand.nextRangeF(-3000.f, 3000.f)); } } // identity { mat.reset(); SkScalar dst[3*kTripleCount]; mat.mapHomogeneousPoints(dst, randTriples, kTripleCount); REPORTER_ASSERT(reporter, scalar_array_nearly_equal_relative(randTriples, dst, kTripleCount*3)); } // zero matrix { mat.setAll(0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f); SkScalar dst[3*kTripleCount]; mat.mapHomogeneousPoints(dst, randTriples, kTripleCount); SkScalar zeros[3] = {0.f, 0.f, 0.f}; for (int i = 0; i < kTripleCount; ++i) { REPORTER_ASSERT(reporter, scalar_array_nearly_equal_relative(&dst[i*3], zeros, 3)); } } // zero point { SkScalar zeros[3] = {0.f, 0.f, 0.f}; for (int i = 0; i < kMatrixCount; ++i) { SkScalar dst[3]; mats[i].mapHomogeneousPoints(dst, zeros, 1); REPORTER_ASSERT(reporter, scalar_array_nearly_equal_relative(dst, zeros, 3)); } } // doesn't crash with null dst, src, count == 0 { mats[0].mapHomogeneousPoints(NULL, NULL, 0); } // uniform scale of point { mat.setScale(kScale0, kScale0); SkScalar dst[3]; SkScalar src[3] = {randTriples[0], randTriples[1], 1.f}; SkPoint pnt; pnt.set(src[0], src[1]); mat.mapHomogeneousPoints(dst, src, 1); mat.mapPoints(&pnt, &pnt, 1); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst[0], pnt.fX)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst[1], pnt.fY)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst[2], SK_Scalar1)); } // rotation of point { mat.setRotate(kRotation0); SkScalar dst[3]; SkScalar src[3] = {randTriples[0], randTriples[1], 1.f}; SkPoint pnt; pnt.set(src[0], src[1]); mat.mapHomogeneousPoints(dst, src, 1); mat.mapPoints(&pnt, &pnt, 1); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst[0], pnt.fX)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst[1], pnt.fY)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst[2], SK_Scalar1)); } // rotation, scale, rotation of point { mat.setRotate(kRotation1); mat.postScale(kScale0, kScale0); mat.postRotate(kRotation0); SkScalar dst[3]; SkScalar src[3] = {randTriples[0], randTriples[1], 1.f}; SkPoint pnt; pnt.set(src[0], src[1]); mat.mapHomogeneousPoints(dst, src, 1); mat.mapPoints(&pnt, &pnt, 1); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst[0], pnt.fX)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst[1], pnt.fY)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst[2], SK_Scalar1)); } // compare with naive approach { for (int i = 0; i < kMatrixCount; ++i) { for (int j = 0; j < kTripleCount; ++j) { SkScalar dst[3]; mats[i].mapHomogeneousPoints(dst, &randTriples[j*3], 1); REPORTER_ASSERT(reporter, naive_homogeneous_mapping(mats[i], &randTriples[j*3], dst)); } } } }
static void test_matrix_min_max_scale(skiatest::Reporter* reporter) { SkScalar scales[2]; bool success; SkMatrix identity; identity.reset(); REPORTER_ASSERT(reporter, SK_Scalar1 == identity.getMinScale()); REPORTER_ASSERT(reporter, SK_Scalar1 == identity.getMaxScale()); success = identity.getMinMaxScales(scales); REPORTER_ASSERT(reporter, success && SK_Scalar1 == scales[0] && SK_Scalar1 == scales[1]); SkMatrix scale; scale.setScale(SK_Scalar1 * 2, SK_Scalar1 * 4); REPORTER_ASSERT(reporter, SK_Scalar1 * 2 == scale.getMinScale()); REPORTER_ASSERT(reporter, SK_Scalar1 * 4 == scale.getMaxScale()); success = scale.getMinMaxScales(scales); REPORTER_ASSERT(reporter, success && SK_Scalar1 * 2 == scales[0] && SK_Scalar1 * 4 == scales[1]); SkMatrix rot90Scale; rot90Scale.setRotate(90 * SK_Scalar1); rot90Scale.postScale(SK_Scalar1 / 4, SK_Scalar1 / 2); REPORTER_ASSERT(reporter, SK_Scalar1 / 4 == rot90Scale.getMinScale()); REPORTER_ASSERT(reporter, SK_Scalar1 / 2 == rot90Scale.getMaxScale()); success = rot90Scale.getMinMaxScales(scales); REPORTER_ASSERT(reporter, success && SK_Scalar1 / 4 == scales[0] && SK_Scalar1 / 2 == scales[1]); SkMatrix rotate; rotate.setRotate(128 * SK_Scalar1); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(SK_Scalar1, rotate.getMinScale(), SK_ScalarNearlyZero)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(SK_Scalar1, rotate.getMaxScale(), SK_ScalarNearlyZero)); success = rotate.getMinMaxScales(scales); REPORTER_ASSERT(reporter, success); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(SK_Scalar1, scales[0], SK_ScalarNearlyZero)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(SK_Scalar1, scales[1], SK_ScalarNearlyZero)); SkMatrix translate; translate.setTranslate(10 * SK_Scalar1, -5 * SK_Scalar1); REPORTER_ASSERT(reporter, SK_Scalar1 == translate.getMinScale()); REPORTER_ASSERT(reporter, SK_Scalar1 == translate.getMaxScale()); success = translate.getMinMaxScales(scales); REPORTER_ASSERT(reporter, success && SK_Scalar1 == scales[0] && SK_Scalar1 == scales[1]); SkMatrix perspX; perspX.reset(); perspX.setPerspX(SkScalarToPersp(SK_Scalar1 / 1000)); REPORTER_ASSERT(reporter, -SK_Scalar1 == perspX.getMinScale()); REPORTER_ASSERT(reporter, -SK_Scalar1 == perspX.getMaxScale()); // Verify that getMinMaxScales() doesn't update the scales array on failure. scales[0] = -5; scales[1] = -5; success = perspX.getMinMaxScales(scales); REPORTER_ASSERT(reporter, !success && -5 * SK_Scalar1 == scales[0] && -5 * SK_Scalar1 == scales[1]); SkMatrix perspY; perspY.reset(); perspY.setPerspY(SkScalarToPersp(-SK_Scalar1 / 500)); REPORTER_ASSERT(reporter, -SK_Scalar1 == perspY.getMinScale()); REPORTER_ASSERT(reporter, -SK_Scalar1 == perspY.getMaxScale()); scales[0] = -5; scales[1] = -5; success = perspY.getMinMaxScales(scales); REPORTER_ASSERT(reporter, !success && -5 * SK_Scalar1 == scales[0] && -5 * SK_Scalar1 == scales[1]); SkMatrix baseMats[] = {scale, rot90Scale, rotate, translate, perspX, perspY}; SkMatrix mats[2*SK_ARRAY_COUNT(baseMats)]; for (size_t i = 0; i < SK_ARRAY_COUNT(baseMats); ++i) { mats[i] = baseMats[i]; bool invertable = mats[i].invert(&mats[i + SK_ARRAY_COUNT(baseMats)]); REPORTER_ASSERT(reporter, invertable); } SkRandom rand; for (int m = 0; m < 1000; ++m) { SkMatrix mat; mat.reset(); for (int i = 0; i < 4; ++i) { int x = rand.nextU() % SK_ARRAY_COUNT(mats); mat.postConcat(mats[x]); } SkScalar minScale = mat.getMinScale(); SkScalar maxScale = mat.getMaxScale(); REPORTER_ASSERT(reporter, (minScale < 0) == (maxScale < 0)); REPORTER_ASSERT(reporter, (maxScale < 0) == mat.hasPerspective()); SkScalar scales[2]; bool success = mat.getMinMaxScales(scales); REPORTER_ASSERT(reporter, success == !mat.hasPerspective()); REPORTER_ASSERT(reporter, !success || (scales[0] == minScale && scales[1] == maxScale)); if (mat.hasPerspective()) { m -= 1; // try another non-persp matrix continue; } // test a bunch of vectors. All should be scaled by between minScale and maxScale // (modulo some error) and we should find a vector that is scaled by almost each. static const SkScalar gVectorScaleTol = (105 * SK_Scalar1) / 100; static const SkScalar gCloseScaleTol = (97 * SK_Scalar1) / 100; SkScalar max = 0, min = SK_ScalarMax; SkVector vectors[1000]; for (size_t i = 0; i < SK_ARRAY_COUNT(vectors); ++i) { vectors[i].fX = rand.nextSScalar1(); vectors[i].fY = rand.nextSScalar1(); if (!vectors[i].normalize()) { i -= 1; continue; } } mat.mapVectors(vectors, SK_ARRAY_COUNT(vectors)); for (size_t i = 0; i < SK_ARRAY_COUNT(vectors); ++i) { SkScalar d = vectors[i].length(); REPORTER_ASSERT(reporter, SkScalarDiv(d, maxScale) < gVectorScaleTol); REPORTER_ASSERT(reporter, SkScalarDiv(minScale, d) < gVectorScaleTol); if (max < d) { max = d; } if (min > d) { min = d; } } REPORTER_ASSERT(reporter, SkScalarDiv(max, maxScale) >= gCloseScaleTol); REPORTER_ASSERT(reporter, SkScalarDiv(minScale, min) >= gCloseScaleTol); } }
static bool nearly_equal(const SkPoint& a, const SkPoint& b) { return SkScalarNearlyEqual(a.fX, b.fX) && SkScalarNearlyEqual(a.fY, b.fY); }
static void TestPathMeasure(skiatest::Reporter* reporter) { SkPath path; path.moveTo(0, 0); path.lineTo(SK_Scalar1, 0); path.lineTo(SK_Scalar1, SK_Scalar1); path.lineTo(0, SK_Scalar1); SkPathMeasure meas(path, true); SkScalar length = meas.getLength(); SkASSERT(length == SK_Scalar1*4); path.reset(); path.moveTo(0, 0); path.lineTo(SK_Scalar1*3, SK_Scalar1*4); meas.setPath(&path, false); length = meas.getLength(); REPORTER_ASSERT(reporter, length == SK_Scalar1*5); path.reset(); path.addCircle(0, 0, SK_Scalar1); meas.setPath(&path, true); length = meas.getLength(); // SkDebugf("circle arc-length = %g\n", length); for (int i = 0; i < 8; i++) { SkScalar d = length * i / 8; SkPoint p; SkVector v; meas.getPosTan(d, &p, &v); #if 0 SkDebugf("circle arc-length=%g, pos[%g %g] tan[%g %g]\n", d, p.fX, p.fY, v.fX, v.fY); #endif } // Test the behavior following a close not followed by a move. path.reset(); path.lineTo(SK_Scalar1, 0); path.lineTo(SK_Scalar1, SK_Scalar1); path.lineTo(0, SK_Scalar1); path.close(); path.lineTo(-SK_Scalar1, 0); meas.setPath(&path, false); length = meas.getLength(); REPORTER_ASSERT(reporter, length == SK_Scalar1 * 4); meas.nextContour(); length = meas.getLength(); REPORTER_ASSERT(reporter, length == SK_Scalar1); SkPoint position; SkVector tangent; REPORTER_ASSERT(reporter, meas.getPosTan(SK_ScalarHalf, &position, &tangent)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(position.fX, -SK_ScalarHalf, SK_Scalar1 * 0.0001)); REPORTER_ASSERT(reporter, position.fY == 0); REPORTER_ASSERT(reporter, tangent.fX == -SK_Scalar1); REPORTER_ASSERT(reporter, tangent.fY == 0); // Test degenerate paths path.reset(); path.moveTo(0, 0); path.lineTo(0, 0); path.lineTo(SK_Scalar1, 0); path.quadTo(SK_Scalar1, 0, SK_Scalar1, 0); path.quadTo(SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1 * 2); path.cubicTo(SK_Scalar1, SK_Scalar1 * 2, SK_Scalar1, SK_Scalar1 * 2, SK_Scalar1, SK_Scalar1 * 2); path.cubicTo(SK_Scalar1*2, SK_Scalar1 * 2, SK_Scalar1*3, SK_Scalar1 * 2, SK_Scalar1*4, SK_Scalar1 * 2); meas.setPath(&path, false); length = meas.getLength(); REPORTER_ASSERT(reporter, length == SK_Scalar1 * 6); REPORTER_ASSERT(reporter, meas.getPosTan(SK_ScalarHalf, &position, &tangent)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(position.fX, SK_ScalarHalf, SK_Scalar1 * 0.0001)); REPORTER_ASSERT(reporter, position.fY == 0); REPORTER_ASSERT(reporter, tangent.fX == SK_Scalar1); REPORTER_ASSERT(reporter, tangent.fY == 0); REPORTER_ASSERT(reporter, meas.getPosTan(SK_Scalar1 * 2.5f, &position, &tangent)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(position.fX, SK_Scalar1, SK_Scalar1 * 0.0001)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(position.fY, SK_Scalar1 * 1.5f)); REPORTER_ASSERT(reporter, tangent.fX == 0); REPORTER_ASSERT(reporter, tangent.fY == SK_Scalar1); REPORTER_ASSERT(reporter, meas.getPosTan(SK_Scalar1 * 4.5f, &position, &tangent)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(position.fX, SK_Scalar1 * 2.5f, SK_Scalar1 * 0.0001)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(position.fY, SK_Scalar1 * 2.0f, SK_Scalar1 * 0.0001)); REPORTER_ASSERT(reporter, tangent.fX == SK_Scalar1); REPORTER_ASSERT(reporter, tangent.fY == 0); path.reset(); path.moveTo(0, 0); path.lineTo(SK_Scalar1, 0); path.moveTo(SK_Scalar1, SK_Scalar1); path.moveTo(SK_Scalar1 * 2, SK_Scalar1 * 2); path.lineTo(SK_Scalar1, SK_Scalar1 * 2); meas.setPath(&path, false); length = meas.getLength(); REPORTER_ASSERT(reporter, length == SK_Scalar1); REPORTER_ASSERT(reporter, meas.getPosTan(SK_ScalarHalf, &position, &tangent)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(position.fX, SK_ScalarHalf, SK_Scalar1 * 0.0001)); REPORTER_ASSERT(reporter, position.fY == 0); REPORTER_ASSERT(reporter, tangent.fX == SK_Scalar1); REPORTER_ASSERT(reporter, tangent.fY == 0); meas.nextContour(); length = meas.getLength(); REPORTER_ASSERT(reporter, length == SK_Scalar1); REPORTER_ASSERT(reporter, meas.getPosTan(SK_ScalarHalf, &position, &tangent)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(position.fX, SK_Scalar1 * 1.5f, SK_Scalar1 * 0.0001)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(position.fY, SK_Scalar1 * 2.0f, SK_Scalar1 * 0.0001)); REPORTER_ASSERT(reporter, tangent.fX == -SK_Scalar1); REPORTER_ASSERT(reporter, tangent.fY == 0); }
static void test_matrix_decomposition(skiatest::Reporter* reporter) { SkMatrix mat; SkScalar rotation0, scaleX, scaleY, rotation1; const float kRotation0 = 15.5f; const float kRotation1 = -50.f; const float kScale0 = 5000.f; const float kScale1 = 0.001f; // identity mat.reset(); REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation0, &scaleX, &scaleY, &rotation1)); REPORTER_ASSERT(reporter, SkScalarNearlyZero(rotation0)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleX, SK_Scalar1)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleY, SK_Scalar1)); REPORTER_ASSERT(reporter, SkScalarNearlyZero(rotation1)); // make sure it doesn't crash if we pass in NULLs REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, NULL, NULL, NULL, NULL)); // rotation only mat.setRotate(kRotation0); REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation0, &scaleX, &scaleY, &rotation1)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(rotation0, SkDegreesToRadians(kRotation0))); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleX, SK_Scalar1)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleY, SK_Scalar1)); REPORTER_ASSERT(reporter, SkScalarNearlyZero(rotation1)); // uniform scale only mat.setScale(kScale0, kScale0); REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation0, &scaleX, &scaleY, &rotation1)); REPORTER_ASSERT(reporter, SkScalarNearlyZero(rotation0)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleX, kScale0)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleY, kScale0)); REPORTER_ASSERT(reporter, SkScalarNearlyZero(rotation1)); // anisotropic scale only mat.setScale(kScale1, kScale0); REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation0, &scaleX, &scaleY, &rotation1)); REPORTER_ASSERT(reporter, SkScalarNearlyZero(rotation0)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleX, kScale1)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleY, kScale0)); REPORTER_ASSERT(reporter, SkScalarNearlyZero(rotation1)); // rotation then uniform scale mat.setRotate(kRotation1); mat.postScale(kScale0, kScale0); REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation0, &scaleX, &scaleY, &rotation1)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(rotation0, SkDegreesToRadians(kRotation1))); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleX, kScale0)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleY, kScale0)); REPORTER_ASSERT(reporter, SkScalarNearlyZero(rotation1)); // uniform scale then rotation mat.setScale(kScale0, kScale0); mat.postRotate(kRotation1); REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation0, &scaleX, &scaleY, &rotation1)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(rotation0, SkDegreesToRadians(kRotation1))); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleX, kScale0)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleY, kScale0)); REPORTER_ASSERT(reporter, SkScalarNearlyZero(rotation1)); // rotation then uniform scale+reflection mat.setRotate(kRotation0); mat.postScale(kScale1, -kScale1); REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation0, &scaleX, &scaleY, &rotation1)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(rotation0, SkDegreesToRadians(kRotation0))); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleX, kScale1)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleY, -kScale1)); REPORTER_ASSERT(reporter, SkScalarNearlyZero(rotation1)); // uniform scale+reflection, then rotate mat.setScale(kScale0, -kScale0); mat.postRotate(kRotation1); REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation0, &scaleX, &scaleY, &rotation1)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(rotation0, SkDegreesToRadians(-kRotation1))); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleX, kScale0)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleY, -kScale0)); REPORTER_ASSERT(reporter, SkScalarNearlyZero(rotation1)); // rotation then anisotropic scale mat.setRotate(kRotation1); mat.postScale(kScale1, kScale0); REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation0, &scaleX, &scaleY, &rotation1)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(rotation0, SkDegreesToRadians(kRotation1))); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleX, kScale1)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleY, kScale0)); REPORTER_ASSERT(reporter, SkScalarNearlyZero(rotation1)); // anisotropic scale then rotation mat.setScale(kScale1, kScale0); mat.postRotate(kRotation0); REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation0, &scaleX, &scaleY, &rotation1)); REPORTER_ASSERT(reporter, SkScalarNearlyZero(rotation0)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleX, kScale1)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleY, kScale0)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(rotation1, SkDegreesToRadians(kRotation0))); // rotation, uniform scale, then different rotation mat.setRotate(kRotation1); mat.postScale(kScale0, kScale0); mat.postRotate(kRotation0); REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation0, &scaleX, &scaleY, &rotation1)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(rotation0, SkDegreesToRadians(kRotation0 + kRotation1))); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleX, kScale0)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleY, kScale0)); REPORTER_ASSERT(reporter, SkScalarNearlyZero(rotation1)); // rotation, anisotropic scale, then different rotation mat.setRotate(kRotation0); mat.postScale(kScale1, kScale0); mat.postRotate(kRotation1); REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation0, &scaleX, &scaleY, &rotation1)); // Because of the shear/skew we won't get the same results, so we need to multiply it out. // Generating the matrices requires doing a radian-to-degree calculation, then degree-to-radian // calculation (in setRotate()), which adds error, so this just computes the matrix elements // directly. SkScalar c0; SkScalar s0 = SkScalarSinCos(rotation0, &c0); SkScalar c1; SkScalar s1 = SkScalarSinCos(rotation1, &c1); // We do a relative check here because large scale factors cause problems with an absolute check REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMScaleX], scaleX*c0*c1 - scaleY*s0*s1)); REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMSkewX], -scaleX*s0*c1 - scaleY*c0*s1)); REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMSkewY], scaleX*c0*s1 + scaleY*s0*c1)); REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMScaleY], -scaleX*s0*s1 + scaleY*c0*c1)); // try some random matrices SkMWCRandom rand; for (int m = 0; m < 1000; ++m) { SkScalar rot0 = rand.nextRangeF(-SK_ScalarPI, SK_ScalarPI); SkScalar sx = rand.nextRangeF(-3000.f, 3000.f); SkScalar sy = rand.nextRangeF(-3000.f, 3000.f); SkScalar rot1 = rand.nextRangeF(-SK_ScalarPI, SK_ScalarPI); mat.setRotate(rot0); mat.postScale(sx, sy); mat.postRotate(rot1); if (SkDecomposeUpper2x2(mat, &rotation0, &scaleX, &scaleY, &rotation1)) { SkScalar c0; SkScalar s0 = SkScalarSinCos(rotation0, &c0); SkScalar c1; SkScalar s1 = SkScalarSinCos(rotation1, &c1); REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMScaleX], scaleX*c0*c1 - scaleY*s0*s1)); REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMSkewX], -scaleX*s0*c1 - scaleY*c0*s1)); REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMSkewY], scaleX*c0*s1 + scaleY*s0*c1)); REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMScaleY], -scaleX*s0*s1 + scaleY*c0*c1)); } else { // if the matrix is degenerate, the basis vectors should be near-parallel or near-zero SkScalar perpdot = mat[SkMatrix::kMScaleX]*mat[SkMatrix::kMScaleY] - mat[SkMatrix::kMSkewX]*mat[SkMatrix::kMSkewY]; REPORTER_ASSERT(reporter, SkScalarNearlyZero(perpdot)); } } // translation shouldn't affect this mat.postTranslate(-1000.f, 1000.f); REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation0, &scaleX, &scaleY, &rotation1)); s0 = SkScalarSinCos(rotation0, &c0); s1 = SkScalarSinCos(rotation1, &c1); REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMScaleX], scaleX*c0*c1 - scaleY*s0*s1)); REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMSkewX], -scaleX*s0*c1 - scaleY*c0*s1)); REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMSkewY], scaleX*c0*s1 + scaleY*s0*c1)); REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMScaleY], -scaleX*s0*s1 + scaleY*c0*c1)); // perspective shouldn't affect this mat[SkMatrix::kMPersp0] = 12.f; mat[SkMatrix::kMPersp1] = 4.f; mat[SkMatrix::kMPersp2] = 1872.f; REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation0, &scaleX, &scaleY, &rotation1)); s0 = SkScalarSinCos(rotation0, &c0); s1 = SkScalarSinCos(rotation1, &c1); REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMScaleX], scaleX*c0*c1 - scaleY*s0*s1)); REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMSkewX], -scaleX*s0*c1 - scaleY*c0*s1)); REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMSkewY], scaleX*c0*s1 + scaleY*s0*c1)); REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMScaleY], -scaleX*s0*s1 + scaleY*c0*c1)); // rotation, anisotropic scale + reflection, then different rotation mat.setRotate(kRotation0); mat.postScale(-kScale1, kScale0); mat.postRotate(kRotation1); REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation0, &scaleX, &scaleY, &rotation1)); s0 = SkScalarSinCos(rotation0, &c0); s1 = SkScalarSinCos(rotation1, &c1); REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMScaleX], scaleX*c0*c1 - scaleY*s0*s1)); REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMSkewX], -scaleX*s0*c1 - scaleY*c0*s1)); REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMSkewY], scaleX*c0*s1 + scaleY*s0*c1)); REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMScaleY], -scaleX*s0*s1 + scaleY*c0*c1)); // degenerate matrices // mostly zero entries mat.reset(); mat[SkMatrix::kMScaleX] = 0.f; REPORTER_ASSERT(reporter, !SkDecomposeUpper2x2(mat, &rotation0, &scaleX, &scaleY, &rotation1)); mat.reset(); mat[SkMatrix::kMScaleY] = 0.f; REPORTER_ASSERT(reporter, !SkDecomposeUpper2x2(mat, &rotation0, &scaleX, &scaleY, &rotation1)); mat.reset(); // linearly dependent entries mat[SkMatrix::kMScaleX] = 1.f; mat[SkMatrix::kMSkewX] = 2.f; mat[SkMatrix::kMSkewY] = 4.f; mat[SkMatrix::kMScaleY] = 8.f; REPORTER_ASSERT(reporter, !SkDecomposeUpper2x2(mat, &rotation0, &scaleX, &scaleY, &rotation1)); }
bool SkBitmapProcState::possiblyScaleImage() { SkASSERT(NULL == fBitmap); fAdjustedMatrix = false; if (fFilterLevel <= SkPaint::kLow_FilterLevel) { return false; } // Check to see if the transformation matrix is simple, and if we're // doing high quality scaling. If so, do the bitmap scale here and // remove the (non-fractional) scaling component from the matrix. SkScalar invScaleX = fInvMatrix.getScaleX(); SkScalar invScaleY = fInvMatrix.getScaleY(); float trueDestWidth = fOrigBitmap.width() / invScaleX; float trueDestHeight = fOrigBitmap.height() / invScaleY; #ifndef SK_IGNORE_PROPER_FRACTIONAL_SCALING float roundedDestWidth = SkScalarRoundToScalar(trueDestWidth); float roundedDestHeight = SkScalarRoundToScalar(trueDestHeight); #else float roundedDestWidth = trueDestWidth; float roundedDestHeight = trueDestHeight; #endif if (SkPaint::kHigh_FilterLevel == fFilterLevel && fInvMatrix.getType() <= (SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask) && kN32_SkColorType == fOrigBitmap.colorType() && cache_size_okay(fOrigBitmap, fInvMatrix)) { if (SkScalarNearlyEqual(invScaleX,1.0f) && SkScalarNearlyEqual(invScaleY,1.0f)) { // short-circuit identity scaling; the output is supposed to // be the same as the input, so we might as well go fast. // Note(humper): We could also probably do this if the scales // are close to -1 as well, since the flip doesn't require // any fancy re-sampling... // Set our filter level to low -- the only post-filtering this // image might require is some interpolation if the translation // is fractional. fFilterLevel = SkPaint::kLow_FilterLevel; return false; } if (!SkBitmapCache::Find(fOrigBitmap, roundedDestWidth, roundedDestHeight, &fScaledBitmap)) { // All the criteria are met; let's make a new bitmap. if (!SkBitmapScaler::Resize(&fScaledBitmap, fOrigBitmap, SkBitmapScaler::RESIZE_BEST, roundedDestWidth, roundedDestHeight, SkResourceCache::GetAllocator())) { // we failed to create fScaledBitmap, so just return and let // the scanline proc handle it. return false; } SkASSERT(fScaledBitmap.getPixels()); fScaledBitmap.setImmutable(); SkBitmapCache::Add(fOrigBitmap, roundedDestWidth, roundedDestHeight, fScaledBitmap); } SkASSERT(fScaledBitmap.getPixels()); fBitmap = &fScaledBitmap; // set the inv matrix type to translate-only; fInvMatrix.setTranslate(fInvMatrix.getTranslateX() / fInvMatrix.getScaleX(), fInvMatrix.getTranslateY() / fInvMatrix.getScaleY()); #ifndef SK_IGNORE_PROPER_FRACTIONAL_SCALING // reintroduce any fractional scaling missed by our integral scale done above. float fractionalScaleX = roundedDestWidth/trueDestWidth; float fractionalScaleY = roundedDestHeight/trueDestHeight; fInvMatrix.postScale(fractionalScaleX, fractionalScaleY); #endif fAdjustedMatrix = true; // Set our filter level to low -- the only post-filtering this // image might require is some interpolation if the translation // is fractional or if there's any remaining scaling to be done. fFilterLevel = SkPaint::kLow_FilterLevel; return true; } /* * If High, then our special-case for scale-only did not take, and so we * have to make a choice: * 1. fall back on mipmaps + bilerp * 2. fall back on scanline bicubic filter * For now, we compute the "scale" value from the matrix, and have a * threshold to decide when bicubic is better, and when mips are better. * No doubt a fancier decision tree could be used uere. * * If Medium, then we just try to build a mipmap and select a level, * setting the filter-level to kLow to signal that we just need bilerp * to process the selected level. */ SkScalar scaleSqd = effective_matrix_scale_sqrd(fInvMatrix); if (SkPaint::kHigh_FilterLevel == fFilterLevel) { // Set the limit at 0.25 for the CTM... if the CTM is scaling smaller // than this, then the mipmaps quality may be greater (certainly faster) // so we only keep High quality if the scale is greater than this. // // Since we're dealing with the inverse, we compare against its inverse. const SkScalar bicubicLimit = 4.0f; const SkScalar bicubicLimitSqd = bicubicLimit * bicubicLimit; if (scaleSqd < bicubicLimitSqd) { // use bicubic scanline return false; } // else set the filter-level to Medium, since we're scaling down and // want to reqeust mipmaps fFilterLevel = SkPaint::kMedium_FilterLevel; } SkASSERT(SkPaint::kMedium_FilterLevel == fFilterLevel); /** * Medium quality means use a mipmap for down-scaling, and just bilper * for upscaling. Since we're examining the inverse matrix, we look for * a scale > 1 to indicate down scaling by the CTM. */ if (scaleSqd > SK_Scalar1) { fCurrMip.reset(SkMipMapCache::FindAndRef(fOrigBitmap)); if (NULL == fCurrMip.get()) { fCurrMip.reset(SkMipMap::Build(fOrigBitmap)); if (NULL == fCurrMip.get()) { return false; } SkMipMapCache::Add(fOrigBitmap, fCurrMip); } SkScalar levelScale = SkScalarInvert(SkScalarSqrt(scaleSqd)); SkMipMap::Level level; if (fCurrMip->extractLevel(levelScale, &level)) { SkScalar invScaleFixup = level.fScale; fInvMatrix.postScale(invScaleFixup, invScaleFixup); const SkImageInfo info = fOrigBitmap.info().makeWH(level.fWidth, level.fHeight); // todo: if we could wrap the fCurrMip in a pixelref, then we could just install // that here, and not need to explicitly track it ourselves. fScaledBitmap.installPixels(info, level.fPixels, level.fRowBytes); fBitmap = &fScaledBitmap; fFilterLevel = SkPaint::kLow_FilterLevel; return true; } } return false; }
static bool equal(const SkRect& a, const SkRect& b) { return SkScalarNearlyEqual(a.left(), b.left()) && SkScalarNearlyEqual(a.top(), b.top()) && SkScalarNearlyEqual(a.right(), b.right()) && SkScalarNearlyEqual(a.bottom(), b.bottom()); }
void SkDebugger::getOverviewText(const SkTDArray<double>* typeTimes, double totTime, SkString* overview, int numRuns) { const SkTDArray<SkDrawCommand*>& commands = this->getDrawCommands(); SkTDArray<int> counts; counts.setCount(LAST_DRAWTYPE_ENUM+1); for (int i = 0; i < LAST_DRAWTYPE_ENUM+1; ++i) { counts[i] = 0; } for (int i = 0; i < commands.count(); i++) { counts[commands[i]->getType()]++; } overview->reset(); int total = 0; #ifdef SK_DEBUG double totPercent = 0, tempSum = 0; #endif for (int i = 0; i < LAST_DRAWTYPE_ENUM+1; ++i) { if (0 == counts[i]) { // if there were no commands of this type then they should've consumed no time SkASSERT(NULL == typeTimes || 0.0 == (*typeTimes)[i]); continue; } overview->append(SkDrawCommand::GetCommandString((DrawType) i)); overview->append(": "); overview->appendS32(counts[i]); if (NULL != typeTimes && totTime >= 0.0) { overview->append(" - "); overview->appendf("%.2f", (*typeTimes)[i]/(float)numRuns); overview->append("ms"); overview->append(" - "); double percent = 100.0*(*typeTimes)[i]/totTime; overview->appendf("%.2f", percent); overview->append("%"); #ifdef SK_DEBUG totPercent += percent; tempSum += (*typeTimes)[i]; #endif } overview->append("<br/>"); total += counts[i]; } #ifdef SK_DEBUG if (NULL != typeTimes) { SkASSERT(SkScalarNearlyEqual(SkDoubleToScalar(totPercent), SkDoubleToScalar(100.0))); SkASSERT(SkScalarNearlyEqual(SkDoubleToScalar(tempSum), SkDoubleToScalar(totTime))); } #endif if (totTime > 0.0) { overview->append("Total Time: "); overview->appendf("%.2f", totTime/(float)numRuns); overview->append("ms"); #ifdef SK_DEBUG overview->append(" "); overview->appendScalar(SkDoubleToScalar(totPercent)); overview->append("% "); #endif overview->append("<br/>"); } SkString totalStr; totalStr.append("Total Draw Commands: "); totalStr.appendScalar(SkDoubleToScalar(total)); totalStr.append("<br/>"); overview->insert(0, totalStr); overview->append("<br/>"); overview->append("SkPicture Width: "); overview->appendS32(pictureWidth()); overview->append("px<br/>"); overview->append("SkPicture Height: "); overview->appendS32(pictureHeight()); overview->append("px"); }
// Called to test various transforms on a single SkRRect. static void test_transform_helper(skiatest::Reporter* reporter, const SkRRect& orig) { SkRRect dst; dst.setEmpty(); // The identity matrix will duplicate the rrect. bool success = orig.transform(SkMatrix::I(), &dst); REPORTER_ASSERT(reporter, success); REPORTER_ASSERT(reporter, orig == dst); // Skew and Perspective make transform fail. SkMatrix matrix; matrix.reset(); matrix.setSkewX(SkIntToScalar(2)); assert_transform_failure(reporter, orig, matrix); matrix.reset(); matrix.setSkewY(SkIntToScalar(3)); assert_transform_failure(reporter, orig, matrix); matrix.reset(); matrix.setPerspX(4); assert_transform_failure(reporter, orig, matrix); matrix.reset(); matrix.setPerspY(5); assert_transform_failure(reporter, orig, matrix); // Rotation fails. matrix.reset(); matrix.setRotate(SkIntToScalar(90)); assert_transform_failure(reporter, orig, matrix); matrix.setRotate(SkIntToScalar(37)); assert_transform_failure(reporter, orig, matrix); // Translate will keep the rect moved, but otherwise the same. matrix.reset(); SkScalar translateX = SkIntToScalar(32); SkScalar translateY = SkIntToScalar(15); matrix.setTranslateX(translateX); matrix.setTranslateY(translateY); dst.setEmpty(); success = orig.transform(matrix, &dst); REPORTER_ASSERT(reporter, success); for (int i = 0; i < 4; ++i) { REPORTER_ASSERT(reporter, orig.radii((SkRRect::Corner) i) == dst.radii((SkRRect::Corner) i)); } REPORTER_ASSERT(reporter, orig.rect().width() == dst.rect().width()); REPORTER_ASSERT(reporter, orig.rect().height() == dst.rect().height()); REPORTER_ASSERT(reporter, dst.rect().left() == orig.rect().left() + translateX); REPORTER_ASSERT(reporter, dst.rect().top() == orig.rect().top() + translateY); // Keeping the translation, but adding skew will make transform fail. matrix.setSkewY(SkIntToScalar(7)); assert_transform_failure(reporter, orig, matrix); // Scaling in -x will flip the round rect horizontally. matrix.reset(); matrix.setScaleX(SkIntToScalar(-1)); dst.setEmpty(); success = orig.transform(matrix, &dst); REPORTER_ASSERT(reporter, success); { GET_RADII; // Radii have swapped in x. REPORTER_ASSERT(reporter, origUL == dstUR); REPORTER_ASSERT(reporter, origUR == dstUL); REPORTER_ASSERT(reporter, origLR == dstLL); REPORTER_ASSERT(reporter, origLL == dstLR); } // Width and height remain the same. REPORTER_ASSERT(reporter, orig.rect().width() == dst.rect().width()); REPORTER_ASSERT(reporter, orig.rect().height() == dst.rect().height()); // Right and left have swapped (sort of) REPORTER_ASSERT(reporter, orig.rect().right() == -dst.rect().left()); // Top has stayed the same. REPORTER_ASSERT(reporter, orig.rect().top() == dst.rect().top()); // Keeping the scale, but adding a persp will make transform fail. matrix.setPerspX(7); assert_transform_failure(reporter, orig, matrix); // Scaling in -y will flip the round rect vertically. matrix.reset(); matrix.setScaleY(SkIntToScalar(-1)); dst.setEmpty(); success = orig.transform(matrix, &dst); REPORTER_ASSERT(reporter, success); { GET_RADII; // Radii have swapped in y. REPORTER_ASSERT(reporter, origUL == dstLL); REPORTER_ASSERT(reporter, origUR == dstLR); REPORTER_ASSERT(reporter, origLR == dstUR); REPORTER_ASSERT(reporter, origLL == dstUL); } // Width and height remain the same. REPORTER_ASSERT(reporter, orig.rect().width() == dst.rect().width()); REPORTER_ASSERT(reporter, orig.rect().height() == dst.rect().height()); // Top and bottom have swapped (sort of) REPORTER_ASSERT(reporter, orig.rect().top() == -dst.rect().bottom()); // Left has stayed the same. REPORTER_ASSERT(reporter, orig.rect().left() == dst.rect().left()); // Scaling in -x and -y will swap in both directions. matrix.reset(); matrix.setScaleY(SkIntToScalar(-1)); matrix.setScaleX(SkIntToScalar(-1)); dst.setEmpty(); success = orig.transform(matrix, &dst); REPORTER_ASSERT(reporter, success); { GET_RADII; REPORTER_ASSERT(reporter, origUL == dstLR); REPORTER_ASSERT(reporter, origUR == dstLL); REPORTER_ASSERT(reporter, origLR == dstUL); REPORTER_ASSERT(reporter, origLL == dstUR); } // Width and height remain the same. REPORTER_ASSERT(reporter, orig.rect().width() == dst.rect().width()); REPORTER_ASSERT(reporter, orig.rect().height() == dst.rect().height()); REPORTER_ASSERT(reporter, orig.rect().top() == -dst.rect().bottom()); REPORTER_ASSERT(reporter, orig.rect().right() == -dst.rect().left()); // Scale in both directions. SkScalar xScale = SkIntToScalar(3); SkScalar yScale = 3.2f; matrix.reset(); matrix.setScaleX(xScale); matrix.setScaleY(yScale); dst.setEmpty(); success = orig.transform(matrix, &dst); REPORTER_ASSERT(reporter, success); // Radii are scaled. for (int i = 0; i < 4; ++i) { REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst.radii((SkRRect::Corner) i).fX, SkScalarMul(orig.radii((SkRRect::Corner) i).fX, xScale))); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst.radii((SkRRect::Corner) i).fY, SkScalarMul(orig.radii((SkRRect::Corner) i).fY, yScale))); } REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst.rect().width(), SkScalarMul(orig.rect().width(), xScale))); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst.rect().height(), SkScalarMul(orig.rect().height(), yScale))); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst.rect().left(), SkScalarMul(orig.rect().left(), xScale))); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst.rect().top(), SkScalarMul(orig.rect().top(), yScale))); }
static void TestPathMeasure(skiatest::Reporter* reporter) { SkPath path; path.moveTo(0, 0); path.lineTo(SK_Scalar1, 0); path.lineTo(SK_Scalar1, SK_Scalar1); path.lineTo(0, SK_Scalar1); SkPathMeasure meas(path, true); SkScalar length = meas.getLength(); SkASSERT(length == SK_Scalar1*4); path.reset(); path.moveTo(0, 0); path.lineTo(SK_Scalar1*3, SK_Scalar1*4); meas.setPath(&path, false); length = meas.getLength(); REPORTER_ASSERT(reporter, length == SK_Scalar1*5); path.reset(); path.addCircle(0, 0, SK_Scalar1); meas.setPath(&path, true); length = meas.getLength(); // SkDebugf("circle arc-length = %g\n", length); // Test the behavior following a close not followed by a move. path.reset(); path.lineTo(SK_Scalar1, 0); path.lineTo(SK_Scalar1, SK_Scalar1); path.lineTo(0, SK_Scalar1); path.close(); path.lineTo(-SK_Scalar1, 0); meas.setPath(&path, false); length = meas.getLength(); REPORTER_ASSERT(reporter, length == SK_Scalar1 * 4); meas.nextContour(); length = meas.getLength(); REPORTER_ASSERT(reporter, length == SK_Scalar1); SkPoint position; SkVector tangent; REPORTER_ASSERT(reporter, meas.getPosTan(SK_ScalarHalf, &position, &tangent)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(position.fX, -SK_ScalarHalf, SkFloatToScalar(0.0001f))); REPORTER_ASSERT(reporter, position.fY == 0); REPORTER_ASSERT(reporter, tangent.fX == -SK_Scalar1); REPORTER_ASSERT(reporter, tangent.fY == 0); // Test degenerate paths path.reset(); path.moveTo(0, 0); path.lineTo(0, 0); path.lineTo(SK_Scalar1, 0); path.quadTo(SK_Scalar1, 0, SK_Scalar1, 0); path.quadTo(SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1 * 2); path.cubicTo(SK_Scalar1, SK_Scalar1 * 2, SK_Scalar1, SK_Scalar1 * 2, SK_Scalar1, SK_Scalar1 * 2); path.cubicTo(SK_Scalar1*2, SK_Scalar1 * 2, SK_Scalar1*3, SK_Scalar1 * 2, SK_Scalar1*4, SK_Scalar1 * 2); meas.setPath(&path, false); length = meas.getLength(); REPORTER_ASSERT(reporter, length == SK_Scalar1 * 6); REPORTER_ASSERT(reporter, meas.getPosTan(SK_ScalarHalf, &position, &tangent)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(position.fX, SK_ScalarHalf, SkFloatToScalar(0.0001f))); REPORTER_ASSERT(reporter, position.fY == 0); REPORTER_ASSERT(reporter, tangent.fX == SK_Scalar1); REPORTER_ASSERT(reporter, tangent.fY == 0); REPORTER_ASSERT(reporter, meas.getPosTan(SkFloatToScalar(2.5f), &position, &tangent)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(position.fX, SK_Scalar1, SkFloatToScalar(0.0001f))); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(position.fY, SkFloatToScalar(1.5f))); REPORTER_ASSERT(reporter, tangent.fX == 0); REPORTER_ASSERT(reporter, tangent.fY == SK_Scalar1); REPORTER_ASSERT(reporter, meas.getPosTan(SkFloatToScalar(4.5f), &position, &tangent)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(position.fX, SkFloatToScalar(2.5f), SkFloatToScalar(0.0001f))); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(position.fY, SkFloatToScalar(2.0f), SkFloatToScalar(0.0001f))); REPORTER_ASSERT(reporter, tangent.fX == SK_Scalar1); REPORTER_ASSERT(reporter, tangent.fY == 0); path.reset(); path.moveTo(0, 0); path.lineTo(SK_Scalar1, 0); path.moveTo(SK_Scalar1, SK_Scalar1); path.moveTo(SK_Scalar1 * 2, SK_Scalar1 * 2); path.lineTo(SK_Scalar1, SK_Scalar1 * 2); meas.setPath(&path, false); length = meas.getLength(); REPORTER_ASSERT(reporter, length == SK_Scalar1); REPORTER_ASSERT(reporter, meas.getPosTan(SK_ScalarHalf, &position, &tangent)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(position.fX, SK_ScalarHalf, SkFloatToScalar(0.0001f))); REPORTER_ASSERT(reporter, position.fY == 0); REPORTER_ASSERT(reporter, tangent.fX == SK_Scalar1); REPORTER_ASSERT(reporter, tangent.fY == 0); meas.nextContour(); length = meas.getLength(); REPORTER_ASSERT(reporter, length == SK_Scalar1); REPORTER_ASSERT(reporter, meas.getPosTan(SK_ScalarHalf, &position, &tangent)); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(position.fX, SkFloatToScalar(1.5f), SkFloatToScalar(0.0001f))); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(position.fY, SkFloatToScalar(2.0f), SkFloatToScalar(0.0001f))); REPORTER_ASSERT(reporter, tangent.fX == -SK_Scalar1); REPORTER_ASSERT(reporter, tangent.fY == 0); test_small_segment(reporter); test_small_segment2(reporter); test_small_segment3(reporter); }
// Currently asPoints is more restrictive then it needs to be. In the future // we need to: // allow kRound_Cap capping (could allow rotations in the matrix with this) // allow paths to be returned bool SkDashPathEffect::asPoints(PointData* results, const SkPath& src, const SkStrokeRec& rec, const SkMatrix& matrix, const SkRect* cullRect) const { // width < 0 -> fill && width == 0 -> hairline so requiring width > 0 rules both out if (fInitialDashLength < 0 || 0 >= rec.getWidth()) { return false; } // TODO: this next test could be eased up. We could allow any number of // intervals as long as all the ons match and all the offs match. // Additionally, they do not necessarily need to be integers. // We cannot allow arbitrary intervals since we want the returned points // to be uniformly sized. if (fCount != 2 || !SkScalarNearlyEqual(fIntervals[0], fIntervals[1]) || !SkScalarIsInt(fIntervals[0]) || !SkScalarIsInt(fIntervals[1])) { return false; } SkPoint pts[2]; if (!src.isLine(pts)) { return false; } // TODO: this test could be eased up to allow circles if (SkPaint::kButt_Cap != rec.getCap()) { return false; } // TODO: this test could be eased up for circles. Rotations could be allowed. if (!matrix.rectStaysRect()) { return false; } // See if the line can be limited to something plausible. if (!cull_line(pts, rec, matrix, cullRect, fIntervalLength)) { return false; } SkScalar length = SkPoint::Distance(pts[1], pts[0]); SkVector tangent = pts[1] - pts[0]; if (tangent.isZero()) { return false; } tangent.scale(SkScalarInvert(length)); // TODO: make this test for horizontal & vertical lines more robust bool isXAxis = true; if (SkScalarNearlyEqual(SK_Scalar1, tangent.fX) || SkScalarNearlyEqual(-SK_Scalar1, tangent.fX)) { results->fSize.set(SkScalarHalf(fIntervals[0]), SkScalarHalf(rec.getWidth())); } else if (SkScalarNearlyEqual(SK_Scalar1, tangent.fY) || SkScalarNearlyEqual(-SK_Scalar1, tangent.fY)) { results->fSize.set(SkScalarHalf(rec.getWidth()), SkScalarHalf(fIntervals[0])); isXAxis = false; } else if (SkPaint::kRound_Cap != rec.getCap()) { // Angled lines don't have axis-aligned boxes. return false; } if (results) { results->fFlags = 0; SkScalar clampedInitialDashLength = SkMinScalar(length, fInitialDashLength); if (SkPaint::kRound_Cap == rec.getCap()) { results->fFlags |= PointData::kCircles_PointFlag; } results->fNumPoints = 0; SkScalar len2 = length; if (clampedInitialDashLength > 0 || 0 == fInitialDashIndex) { SkASSERT(len2 >= clampedInitialDashLength); if (0 == fInitialDashIndex) { if (clampedInitialDashLength > 0) { if (clampedInitialDashLength >= fIntervals[0]) { ++results->fNumPoints; // partial first dash } len2 -= clampedInitialDashLength; } len2 -= fIntervals[1]; // also skip first space if (len2 < 0) { len2 = 0; } } else { len2 -= clampedInitialDashLength; // skip initial partial empty } } int numMidPoints = SkScalarFloorToInt(len2 / fIntervalLength); results->fNumPoints += numMidPoints; len2 -= numMidPoints * fIntervalLength; bool partialLast = false; if (len2 > 0) { if (len2 < fIntervals[0]) { partialLast = true; } else { ++numMidPoints; ++results->fNumPoints; } } results->fPoints = new SkPoint[results->fNumPoints]; SkScalar distance = 0; int curPt = 0; if (clampedInitialDashLength > 0 || 0 == fInitialDashIndex) { SkASSERT(clampedInitialDashLength <= length); if (0 == fInitialDashIndex) { if (clampedInitialDashLength > 0) { // partial first block SkASSERT(SkPaint::kRound_Cap != rec.getCap()); // can't handle partial circles SkScalar x = pts[0].fX + SkScalarMul(tangent.fX, SkScalarHalf(clampedInitialDashLength)); SkScalar y = pts[0].fY + SkScalarMul(tangent.fY, SkScalarHalf(clampedInitialDashLength)); SkScalar halfWidth, halfHeight; if (isXAxis) { halfWidth = SkScalarHalf(clampedInitialDashLength); halfHeight = SkScalarHalf(rec.getWidth()); } else { halfWidth = SkScalarHalf(rec.getWidth()); halfHeight = SkScalarHalf(clampedInitialDashLength); } if (clampedInitialDashLength < fIntervals[0]) { // This one will not be like the others results->fFirst.addRect(x - halfWidth, y - halfHeight, x + halfWidth, y + halfHeight); } else { SkASSERT(curPt < results->fNumPoints); results->fPoints[curPt].set(x, y); ++curPt; } distance += clampedInitialDashLength; } distance += fIntervals[1]; // skip over the next blank block too } else { distance += clampedInitialDashLength; } } if (0 != numMidPoints) { distance += SkScalarHalf(fIntervals[0]); for (int i = 0; i < numMidPoints; ++i) { SkScalar x = pts[0].fX + SkScalarMul(tangent.fX, distance); SkScalar y = pts[0].fY + SkScalarMul(tangent.fY, distance); SkASSERT(curPt < results->fNumPoints); results->fPoints[curPt].set(x, y); ++curPt; distance += fIntervalLength; } distance -= SkScalarHalf(fIntervals[0]); } if (partialLast) { // partial final block SkASSERT(SkPaint::kRound_Cap != rec.getCap()); // can't handle partial circles SkScalar temp = length - distance; SkASSERT(temp < fIntervals[0]); SkScalar x = pts[0].fX + SkScalarMul(tangent.fX, distance + SkScalarHalf(temp)); SkScalar y = pts[0].fY + SkScalarMul(tangent.fY, distance + SkScalarHalf(temp)); SkScalar halfWidth, halfHeight; if (isXAxis) { halfWidth = SkScalarHalf(temp); halfHeight = SkScalarHalf(rec.getWidth()); } else { halfWidth = SkScalarHalf(rec.getWidth()); halfHeight = SkScalarHalf(temp); } results->fLast.addRect(x - halfWidth, y - halfHeight, x + halfWidth, y + halfHeight); } SkASSERT(curPt == results->fNumPoints); } return true; }
/* * High quality is implemented by performing up-right scale-only filtering and then * using bilerp for any remaining transformations. */ bool SkDefaultBitmapControllerState::processHQRequest(const SkBitmapProvider& provider) { if (fQuality != kHigh_SkFilterQuality) { return false; } // Our default return state is to downgrade the request to Medium, w/ or w/o setting fBitmap // to a valid bitmap. If we succeed, we will set this to Low instead. fQuality = kMedium_SkFilterQuality; if (kN32_SkColorType != provider.info().colorType() || !cache_size_okay(provider, fInvMatrix) || fInvMatrix.hasPerspective()) { return false; // can't handle the reqeust } SkScalar invScaleX = fInvMatrix.getScaleX(); SkScalar invScaleY = fInvMatrix.getScaleY(); if (fInvMatrix.getType() & SkMatrix::kAffine_Mask) { SkSize scale; if (!fInvMatrix.decomposeScale(&scale)) { return false; } invScaleX = scale.width(); invScaleY = scale.height(); } if (SkScalarNearlyEqual(invScaleX, 1) && SkScalarNearlyEqual(invScaleY, 1)) { return false; // no need for HQ } #ifndef SK_SUPPORT_LEGACY_HQ_DOWNSAMPLING if (invScaleX > 1 || invScaleY > 1) { return false; // only use HQ when upsampling } #endif const int dstW = SkScalarRoundToScalar(provider.width() / invScaleX); const int dstH = SkScalarRoundToScalar(provider.height() / invScaleY); const SkBitmapCacheDesc desc = provider.makeCacheDesc(dstW, dstH); if (!SkBitmapCache::FindWH(desc, &fResultBitmap)) { SkBitmap orig; if (!provider.asBitmap(&orig)) { return false; } SkAutoPixmapUnlock src; if (!orig.requestLock(&src)) { return false; } if (!SkBitmapScaler::Resize(&fResultBitmap, src.pixmap(), kHQ_RESIZE_METHOD, dstW, dstH, SkResourceCache::GetAllocator())) { return false; // we failed to create fScaledBitmap } SkASSERT(fResultBitmap.getPixels()); fResultBitmap.setImmutable(); if (!provider.isVolatile()) { if (SkBitmapCache::AddWH(desc, fResultBitmap)) { provider.notifyAddedToCache(); } } } SkASSERT(fResultBitmap.getPixels()); fInvMatrix.postScale(SkIntToScalar(dstW) / provider.width(), SkIntToScalar(dstH) / provider.height()); fQuality = kLow_SkFilterQuality; return true; }