SkDashPathEffect.cpp revision 3ec68f047a1f698bec12e1a270fdf4f62aed9cdb
1 2/* 3 * Copyright 2006 The Android Open Source Project 4 * 5 * Use of this source code is governed by a BSD-style license that can be 6 * found in the LICENSE file. 7 */ 8 9 10#include "SkDashPathEffect.h" 11#include "SkBuffer.h" 12#include "SkPathMeasure.h" 13 14static inline int is_even(int x) { 15 return (~x) << 31; 16} 17 18static SkScalar FindFirstInterval(const SkScalar intervals[], SkScalar phase, 19 int32_t* index) { 20 int i; 21 22 for (i = 0; phase > intervals[i]; i++) { 23 phase -= intervals[i]; 24 } 25 *index = i; 26 return intervals[i] - phase; 27} 28 29SkDashPathEffect::SkDashPathEffect(const SkScalar intervals[], int count, 30 SkScalar phase, bool scaleToFit) 31 : fScaleToFit(scaleToFit) { 32 SkASSERT(intervals); 33 SkASSERT(count > 1 && SkAlign2(count) == count); 34 35 fIntervals = (SkScalar*)sk_malloc_throw(sizeof(SkScalar) * count); 36 fCount = count; 37 38 SkScalar len = 0; 39 for (int i = 0; i < count; i++) { 40 SkASSERT(intervals[i] >= 0); 41 fIntervals[i] = intervals[i]; 42 len += intervals[i]; 43 } 44 fIntervalLength = len; 45 46 // watch out for values that might make us go out of bounds 47 if ((len > 0) && SkScalarIsFinite(phase) && SkScalarIsFinite(len)) { 48 49 // Adjust phase to be between 0 and len, "flipping" phase if negative. 50 // e.g., if len is 100, then phase of -20 (or -120) is equivalent to 80 51 if (phase < 0) { 52 phase = -phase; 53 if (phase > len) { 54 phase = SkScalarMod(phase, len); 55 } 56 phase = len - phase; 57 58 // Due to finite precision, it's possible that phase == len, 59 // even after the subtract (if len >>> phase), so fix that here. 60 // This fixes http://crbug.com/124652 . 61 SkASSERT(phase <= len); 62 if (phase == len) { 63 phase = 0; 64 } 65 } else if (phase >= len) { 66 phase = SkScalarMod(phase, len); 67 } 68 SkASSERT(phase >= 0 && phase < len); 69 70 fInitialDashLength = FindFirstInterval(intervals, phase, &fInitialDashIndex); 71 72 SkASSERT(fInitialDashLength >= 0); 73 SkASSERT(fInitialDashIndex >= 0 && fInitialDashIndex < fCount); 74 } else { 75 fInitialDashLength = -1; // signal bad dash intervals 76 } 77} 78 79SkDashPathEffect::~SkDashPathEffect() { 80 sk_free(fIntervals); 81} 82 83class SpecialLineRec { 84public: 85 bool init(const SkPath& src, SkPath* dst, SkStrokeRec* rec, 86 SkScalar pathLength, 87 int intervalCount, SkScalar intervalLength) { 88 if (rec->isHairlineStyle() || !src.isLine(fPts)) { 89 return false; 90 } 91 92 // can relax this in the future, if we handle square and round caps 93 if (SkPaint::kButt_Cap != rec->getCap()) { 94 return false; 95 } 96 97 fTangent = fPts[1] - fPts[0]; 98 if (fTangent.isZero()) { 99 return false; 100 } 101 102 fPathLength = pathLength; 103 fTangent.scale(SkScalarInvert(pathLength)); 104 fTangent.rotateCCW(&fNormal); 105 fNormal.scale(SkScalarHalf(rec->getWidth())); 106 107 // now estimate how many quads will be added to the path 108 // resulting segments = pathLen * intervalCount / intervalLen 109 // resulting points = 4 * segments 110 111 SkScalar ptCount = SkScalarMulDiv(pathLength, 112 SkIntToScalar(intervalCount), 113 intervalLength); 114 int n = SkScalarCeilToInt(ptCount) << 2; 115 dst->incReserve(n); 116 117 // we will take care of the stroking 118 rec->setFillStyle(); 119 return true; 120 } 121 122 void addSegment(SkScalar d0, SkScalar d1, SkPath* path) const { 123 SkASSERT(d0 < fPathLength); 124 // clamp the segment to our length 125 if (d1 > fPathLength) { 126 d1 = fPathLength; 127 } 128 129 SkScalar x0 = fPts[0].fX + SkScalarMul(fTangent.fX, d0); 130 SkScalar x1 = fPts[0].fX + SkScalarMul(fTangent.fX, d1); 131 SkScalar y0 = fPts[0].fY + SkScalarMul(fTangent.fY, d0); 132 SkScalar y1 = fPts[0].fY + SkScalarMul(fTangent.fY, d1); 133 134 SkPoint pts[4]; 135 pts[0].set(x0 + fNormal.fX, y0 + fNormal.fY); // moveTo 136 pts[1].set(x1 + fNormal.fX, y1 + fNormal.fY); // lineTo 137 pts[2].set(x1 - fNormal.fX, y1 - fNormal.fY); // lineTo 138 pts[3].set(x0 - fNormal.fX, y0 - fNormal.fY); // lineTo 139 140 path->addPoly(pts, SK_ARRAY_COUNT(pts), false); 141 } 142 143private: 144 SkPoint fPts[2]; 145 SkVector fTangent; 146 SkVector fNormal; 147 SkScalar fPathLength; 148}; 149 150bool SkDashPathEffect::filterPath(SkPath* dst, const SkPath& src, 151 SkStrokeRec* rec) { 152 // we do nothing if the src wants to be filled, or if our dashlength is 0 153 if (rec->isFillStyle() || fInitialDashLength < 0) { 154 return false; 155 } 156 157 SkPathMeasure meas(src, false); 158 const SkScalar* intervals = fIntervals; 159 160 SpecialLineRec lineRec; 161 const bool specialLine = lineRec.init(src, dst, rec, meas.getLength(), 162 fCount >> 1, fIntervalLength); 163 164 do { 165 bool skipFirstSegment = meas.isClosed(); 166 bool addedSegment = false; 167 SkScalar length = meas.getLength(); 168 int index = fInitialDashIndex; 169 SkScalar scale = SK_Scalar1; 170 171 if (fScaleToFit) { 172 if (fIntervalLength >= length) { 173 scale = SkScalarDiv(length, fIntervalLength); 174 } else { 175 SkScalar div = SkScalarDiv(length, fIntervalLength); 176 int n = SkScalarFloor(div); 177 scale = SkScalarDiv(length, n * fIntervalLength); 178 } 179 } 180 181 SkScalar distance = 0; 182 SkScalar dlen = SkScalarMul(fInitialDashLength, scale); 183 184 while (distance < length) { 185 SkASSERT(dlen >= 0); 186 addedSegment = false; 187 if (is_even(index) && dlen > 0 && !skipFirstSegment) { 188 addedSegment = true; 189 190 if (specialLine) { 191 lineRec.addSegment(distance, distance + dlen, dst); 192 } else { 193 meas.getSegment(distance, distance + dlen, dst, true); 194 } 195 } 196 distance += dlen; 197 198 // clear this so we only respect it the first time around 199 skipFirstSegment = false; 200 201 // wrap around our intervals array if necessary 202 index += 1; 203 SkASSERT(index <= fCount); 204 if (index == fCount) { 205 index = 0; 206 } 207 208 // fetch our next dlen 209 dlen = SkScalarMul(intervals[index], scale); 210 } 211 212 // extend if we ended on a segment and we need to join up with the (skipped) initial segment 213 if (meas.isClosed() && is_even(fInitialDashIndex) && 214 fInitialDashLength > 0) { 215 meas.getSegment(0, SkScalarMul(fInitialDashLength, scale), dst, !addedSegment); 216 } 217 } while (meas.nextContour()); 218 219 return true; 220} 221 222SkFlattenable::Factory SkDashPathEffect::getFactory() { 223 return fInitialDashLength < 0 ? NULL : CreateProc; 224} 225 226void SkDashPathEffect::flatten(SkFlattenableWriteBuffer& buffer) const { 227 SkASSERT(fInitialDashLength >= 0); 228 229 this->INHERITED::flatten(buffer); 230 buffer.write32(fCount); 231 buffer.write32(fInitialDashIndex); 232 buffer.writeScalar(fInitialDashLength); 233 buffer.writeScalar(fIntervalLength); 234 buffer.write32(fScaleToFit); 235 buffer.writeMul4(fIntervals, fCount * sizeof(fIntervals[0])); 236} 237 238SkFlattenable* SkDashPathEffect::CreateProc(SkFlattenableReadBuffer& buffer) { 239 return SkNEW_ARGS(SkDashPathEffect, (buffer)); 240} 241 242SkDashPathEffect::SkDashPathEffect(SkFlattenableReadBuffer& buffer) { 243 fCount = buffer.readS32(); 244 fInitialDashIndex = buffer.readS32(); 245 fInitialDashLength = buffer.readScalar(); 246 fIntervalLength = buffer.readScalar(); 247 fScaleToFit = (buffer.readS32() != 0); 248 249 fIntervals = (SkScalar*)sk_malloc_throw(sizeof(SkScalar) * fCount); 250 buffer.read(fIntervals, fCount * sizeof(fIntervals[0])); 251} 252 253/////////////////////////////////////////////////////////////////////////////// 254 255SK_DEFINE_FLATTENABLE_REGISTRAR(SkDashPathEffect) 256