1/* 2 * Copyright 2006 The Android Open Source Project 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8#include "SkDashPathEffect.h" 9 10#include "SkDashPathPriv.h" 11#include "SkReadBuffer.h" 12#include "SkWriteBuffer.h" 13 14SkDashPathEffect::SkDashPathEffect(const SkScalar intervals[], int count, 15 SkScalar phase) { 16 SkASSERT(intervals); 17 SkASSERT(count > 1 && SkAlign2(count) == count); 18 19 fIntervals = (SkScalar*)sk_malloc_throw(sizeof(SkScalar) * count); 20 fCount = count; 21 for (int i = 0; i < count; i++) { 22 SkASSERT(intervals[i] >= 0); 23 fIntervals[i] = intervals[i]; 24 } 25 26 // set the internal data members 27 SkDashPath::CalcDashParameters(phase, fIntervals, fCount, &fInitialDashLength, 28 &fInitialDashIndex, &fIntervalLength, &fPhase); 29} 30 31SkDashPathEffect::~SkDashPathEffect() { 32 sk_free(fIntervals); 33} 34 35bool SkDashPathEffect::filterPath(SkPath* dst, const SkPath& src, 36 SkStrokeRec* rec, const SkRect* cullRect) const { 37 return SkDashPath::FilterDashPath(dst, src, rec, cullRect, fIntervals, fCount, 38 fInitialDashLength, fInitialDashIndex, fIntervalLength); 39} 40 41// Currently asPoints is more restrictive then it needs to be. In the future 42// we need to: 43// allow kRound_Cap capping (could allow rotations in the matrix with this) 44// allow paths to be returned 45bool SkDashPathEffect::asPoints(PointData* results, 46 const SkPath& src, 47 const SkStrokeRec& rec, 48 const SkMatrix& matrix, 49 const SkRect* cullRect) const { 50 // width < 0 -> fill && width == 0 -> hairline so requiring width > 0 rules both out 51 if (fInitialDashLength < 0 || 0 >= rec.getWidth()) { 52 return false; 53 } 54 55 // TODO: this next test could be eased up. We could allow any number of 56 // intervals as long as all the ons match and all the offs match. 57 // Additionally, they do not necessarily need to be integers. 58 // We cannot allow arbitrary intervals since we want the returned points 59 // to be uniformly sized. 60 if (fCount != 2 || 61 !SkScalarNearlyEqual(fIntervals[0], fIntervals[1]) || 62 !SkScalarIsInt(fIntervals[0]) || 63 !SkScalarIsInt(fIntervals[1])) { 64 return false; 65 } 66 67 SkPoint pts[2]; 68 69 if (!src.isLine(pts)) { 70 return false; 71 } 72 73 // TODO: this test could be eased up to allow circles 74 if (SkPaint::kButt_Cap != rec.getCap()) { 75 return false; 76 } 77 78 // TODO: this test could be eased up for circles. Rotations could be allowed. 79 if (!matrix.rectStaysRect()) { 80 return false; 81 } 82 83 SkScalar length = SkPoint::Distance(pts[1], pts[0]); 84 85 SkVector tangent = pts[1] - pts[0]; 86 if (tangent.isZero()) { 87 return false; 88 } 89 90 tangent.scale(SkScalarInvert(length)); 91 92 // TODO: make this test for horizontal & vertical lines more robust 93 bool isXAxis = true; 94 if (SK_Scalar1 == tangent.fX || -SK_Scalar1 == tangent.fX) { 95 results->fSize.set(SkScalarHalf(fIntervals[0]), SkScalarHalf(rec.getWidth())); 96 } else if (SK_Scalar1 == tangent.fY || -SK_Scalar1 == tangent.fY) { 97 results->fSize.set(SkScalarHalf(rec.getWidth()), SkScalarHalf(fIntervals[0])); 98 isXAxis = false; 99 } else if (SkPaint::kRound_Cap != rec.getCap()) { 100 // Angled lines don't have axis-aligned boxes. 101 return false; 102 } 103 104 if (NULL != results) { 105 results->fFlags = 0; 106 SkScalar clampedInitialDashLength = SkMinScalar(length, fInitialDashLength); 107 108 if (SkPaint::kRound_Cap == rec.getCap()) { 109 results->fFlags |= PointData::kCircles_PointFlag; 110 } 111 112 results->fNumPoints = 0; 113 SkScalar len2 = length; 114 if (clampedInitialDashLength > 0 || 0 == fInitialDashIndex) { 115 SkASSERT(len2 >= clampedInitialDashLength); 116 if (0 == fInitialDashIndex) { 117 if (clampedInitialDashLength > 0) { 118 if (clampedInitialDashLength >= fIntervals[0]) { 119 ++results->fNumPoints; // partial first dash 120 } 121 len2 -= clampedInitialDashLength; 122 } 123 len2 -= fIntervals[1]; // also skip first space 124 if (len2 < 0) { 125 len2 = 0; 126 } 127 } else { 128 len2 -= clampedInitialDashLength; // skip initial partial empty 129 } 130 } 131 int numMidPoints = SkScalarFloorToInt(SkScalarDiv(len2, fIntervalLength)); 132 results->fNumPoints += numMidPoints; 133 len2 -= numMidPoints * fIntervalLength; 134 bool partialLast = false; 135 if (len2 > 0) { 136 if (len2 < fIntervals[0]) { 137 partialLast = true; 138 } else { 139 ++numMidPoints; 140 ++results->fNumPoints; 141 } 142 } 143 144 results->fPoints = new SkPoint[results->fNumPoints]; 145 146 SkScalar distance = 0; 147 int curPt = 0; 148 149 if (clampedInitialDashLength > 0 || 0 == fInitialDashIndex) { 150 SkASSERT(clampedInitialDashLength <= length); 151 152 if (0 == fInitialDashIndex) { 153 if (clampedInitialDashLength > 0) { 154 // partial first block 155 SkASSERT(SkPaint::kRound_Cap != rec.getCap()); // can't handle partial circles 156 SkScalar x = pts[0].fX + SkScalarMul(tangent.fX, SkScalarHalf(clampedInitialDashLength)); 157 SkScalar y = pts[0].fY + SkScalarMul(tangent.fY, SkScalarHalf(clampedInitialDashLength)); 158 SkScalar halfWidth, halfHeight; 159 if (isXAxis) { 160 halfWidth = SkScalarHalf(clampedInitialDashLength); 161 halfHeight = SkScalarHalf(rec.getWidth()); 162 } else { 163 halfWidth = SkScalarHalf(rec.getWidth()); 164 halfHeight = SkScalarHalf(clampedInitialDashLength); 165 } 166 if (clampedInitialDashLength < fIntervals[0]) { 167 // This one will not be like the others 168 results->fFirst.addRect(x - halfWidth, y - halfHeight, 169 x + halfWidth, y + halfHeight); 170 } else { 171 SkASSERT(curPt < results->fNumPoints); 172 results->fPoints[curPt].set(x, y); 173 ++curPt; 174 } 175 176 distance += clampedInitialDashLength; 177 } 178 179 distance += fIntervals[1]; // skip over the next blank block too 180 } else { 181 distance += clampedInitialDashLength; 182 } 183 } 184 185 if (0 != numMidPoints) { 186 distance += SkScalarHalf(fIntervals[0]); 187 188 for (int i = 0; i < numMidPoints; ++i) { 189 SkScalar x = pts[0].fX + SkScalarMul(tangent.fX, distance); 190 SkScalar y = pts[0].fY + SkScalarMul(tangent.fY, distance); 191 192 SkASSERT(curPt < results->fNumPoints); 193 results->fPoints[curPt].set(x, y); 194 ++curPt; 195 196 distance += fIntervalLength; 197 } 198 199 distance -= SkScalarHalf(fIntervals[0]); 200 } 201 202 if (partialLast) { 203 // partial final block 204 SkASSERT(SkPaint::kRound_Cap != rec.getCap()); // can't handle partial circles 205 SkScalar temp = length - distance; 206 SkASSERT(temp < fIntervals[0]); 207 SkScalar x = pts[0].fX + SkScalarMul(tangent.fX, distance + SkScalarHalf(temp)); 208 SkScalar y = pts[0].fY + SkScalarMul(tangent.fY, distance + SkScalarHalf(temp)); 209 SkScalar halfWidth, halfHeight; 210 if (isXAxis) { 211 halfWidth = SkScalarHalf(temp); 212 halfHeight = SkScalarHalf(rec.getWidth()); 213 } else { 214 halfWidth = SkScalarHalf(rec.getWidth()); 215 halfHeight = SkScalarHalf(temp); 216 } 217 results->fLast.addRect(x - halfWidth, y - halfHeight, 218 x + halfWidth, y + halfHeight); 219 } 220 221 SkASSERT(curPt == results->fNumPoints); 222 } 223 224 return true; 225} 226 227SkPathEffect::DashType SkDashPathEffect::asADash(DashInfo* info) const { 228 if (info) { 229 if (info->fCount >= fCount && NULL != info->fIntervals) { 230 memcpy(info->fIntervals, fIntervals, fCount * sizeof(SkScalar)); 231 } 232 info->fCount = fCount; 233 info->fPhase = fPhase; 234 } 235 return kDash_DashType; 236} 237 238SkFlattenable::Factory SkDashPathEffect::getFactory() const { 239 return CreateProc; 240} 241 242void SkDashPathEffect::flatten(SkWriteBuffer& buffer) const { 243 this->INHERITED::flatten(buffer); 244 buffer.writeScalar(fPhase); 245 buffer.writeScalarArray(fIntervals, fCount); 246} 247 248SkFlattenable* SkDashPathEffect::CreateProc(SkReadBuffer& buffer) { 249 return SkNEW_ARGS(SkDashPathEffect, (buffer)); 250} 251 252SkDashPathEffect::SkDashPathEffect(SkReadBuffer& buffer) : INHERITED(buffer) { 253 bool useOldPic = buffer.isVersionLT(SkReadBuffer::kDashWritesPhaseIntervals_Version); 254 if (useOldPic) { 255 fInitialDashIndex = buffer.readInt(); 256 fInitialDashLength = buffer.readScalar(); 257 fIntervalLength = buffer.readScalar(); 258 buffer.readBool(); // Dummy for old ScalarToFit field 259 } else { 260 fPhase = buffer.readScalar(); 261 } 262 263 fCount = buffer.getArrayCount(); 264 size_t allocSize = sizeof(SkScalar) * fCount; 265 if (buffer.validateAvailable(allocSize)) { 266 fIntervals = (SkScalar*)sk_malloc_throw(allocSize); 267 buffer.readScalarArray(fIntervals, fCount); 268 } else { 269 fIntervals = NULL; 270 } 271 272 if (useOldPic) { 273 fPhase = 0; 274 if (fInitialDashLength != -1) { // Signal for bad dash interval 275 for (int i = 0; i < fInitialDashIndex; ++i) { 276 fPhase += fIntervals[i]; 277 } 278 fPhase += fIntervals[fInitialDashIndex] - fInitialDashLength; 279 } 280 } else { 281 // set the internal data members, fPhase should have been between 0 and intervalLength 282 // when written to buffer so no need to adjust it 283 SkDashPath::CalcDashParameters(fPhase, fIntervals, fCount, &fInitialDashLength, 284 &fInitialDashIndex, &fIntervalLength); 285 } 286} 287