Line data Source code
1 : /*
2 : * Copyright 2011 Google Inc.
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 "SkDevice.h"
9 : #include "SkColorFilter.h"
10 : #include "SkDraw.h"
11 : #include "SkDrawFilter.h"
12 : #include "SkImageCacherator.h"
13 : #include "SkImageFilter.h"
14 : #include "SkImageFilterCache.h"
15 : #include "SkImagePriv.h"
16 : #include "SkImage_Base.h"
17 : #include "SkLatticeIter.h"
18 : #include "SkPatchUtils.h"
19 : #include "SkPathMeasure.h"
20 : #include "SkPathPriv.h"
21 : #include "SkRSXform.h"
22 : #include "SkRasterClip.h"
23 : #include "SkShader.h"
24 : #include "SkSpecialImage.h"
25 : #include "SkTLazy.h"
26 : #include "SkTextBlobRunIterator.h"
27 : #include "SkTextToPathIter.h"
28 : #include "SkVertices.h"
29 :
30 85 : SkBaseDevice::SkBaseDevice(const SkImageInfo& info, const SkSurfaceProps& surfaceProps)
31 : : fInfo(info)
32 85 : , fSurfaceProps(surfaceProps)
33 : {
34 85 : fOrigin.setZero();
35 85 : fCTM.reset();
36 85 : }
37 :
38 22 : void SkBaseDevice::setOrigin(const SkMatrix& globalCTM, int x, int y) {
39 22 : fOrigin.set(x, y);
40 22 : fCTM = globalCTM;
41 22 : fCTM.postTranslate(SkIntToScalar(-x), SkIntToScalar(-y));
42 22 : }
43 :
44 2656 : void SkBaseDevice::setGlobalCTM(const SkMatrix& ctm) {
45 2656 : fCTM = ctm;
46 2656 : if (fOrigin.fX | fOrigin.fY) {
47 207 : fCTM.postTranslate(-SkIntToScalar(fOrigin.fX), -SkIntToScalar(fOrigin.fY));
48 : }
49 2656 : }
50 :
51 0 : bool SkBaseDevice::clipIsWideOpen() const {
52 0 : if (kRect_ClipType == this->onGetClipType()) {
53 0 : SkRegion rgn;
54 0 : this->onAsRgnClip(&rgn);
55 0 : SkASSERT(rgn.isRect());
56 0 : return rgn.getBounds() == SkIRect::MakeWH(this->width(), this->height());
57 : } else {
58 0 : return false;
59 : }
60 : }
61 :
62 22 : SkPixelGeometry SkBaseDevice::CreateInfo::AdjustGeometry(const SkImageInfo& info,
63 : TileUsage tileUsage,
64 : SkPixelGeometry geo,
65 : bool preserveLCDText) {
66 22 : switch (tileUsage) {
67 : case kPossible_TileUsage:
68 : // (we think) for compatibility with old clients, we assume this layer can support LCD
69 : // even though they may not have marked it as opaque... seems like we should update
70 : // our callers (reed/robertphilips).
71 0 : break;
72 : case kNever_TileUsage:
73 22 : if (!preserveLCDText) {
74 0 : geo = kUnknown_SkPixelGeometry;
75 : }
76 22 : break;
77 : }
78 22 : return geo;
79 : }
80 :
81 0 : static inline bool is_int(float x) {
82 0 : return x == (float) sk_float_round2int(x);
83 : }
84 :
85 0 : void SkBaseDevice::drawRegion(const SkRegion& region, const SkPaint& paint) {
86 0 : const SkMatrix& ctm = this->ctm();
87 0 : bool isNonTranslate = ctm.getType() & ~(SkMatrix::kTranslate_Mask);
88 0 : bool complexPaint = paint.getStyle() != SkPaint::kFill_Style || paint.getMaskFilter() ||
89 0 : paint.getPathEffect();
90 0 : bool antiAlias = paint.isAntiAlias() && (!is_int(ctm.getTranslateX()) ||
91 0 : !is_int(ctm.getTranslateY()));
92 0 : if (isNonTranslate || complexPaint || antiAlias) {
93 0 : SkPath path;
94 0 : region.getBoundaryPath(&path);
95 0 : return this->drawPath(path, paint, nullptr, false);
96 : }
97 :
98 0 : SkRegion::Iterator it(region);
99 0 : while (!it.done()) {
100 0 : this->drawRect(SkRect::Make(it.rect()), paint);
101 0 : it.next();
102 : }
103 : }
104 :
105 0 : void SkBaseDevice::drawArc(const SkRect& oval, SkScalar startAngle,
106 : SkScalar sweepAngle, bool useCenter, const SkPaint& paint) {
107 0 : SkPath path;
108 0 : bool isFillNoPathEffect = SkPaint::kFill_Style == paint.getStyle() && !paint.getPathEffect();
109 0 : SkPathPriv::CreateDrawArcPath(&path, oval, startAngle, sweepAngle, useCenter,
110 0 : isFillNoPathEffect);
111 0 : this->drawPath(path, paint);
112 0 : }
113 :
114 0 : void SkBaseDevice::drawDRRect(const SkRRect& outer,
115 : const SkRRect& inner, const SkPaint& paint) {
116 0 : SkPath path;
117 0 : path.addRRect(outer);
118 0 : path.addRRect(inner);
119 0 : path.setFillType(SkPath::kEvenOdd_FillType);
120 0 : path.setIsVolatile(true);
121 :
122 0 : const SkMatrix* preMatrix = nullptr;
123 0 : const bool pathIsMutable = true;
124 0 : this->drawPath(path, paint, preMatrix, pathIsMutable);
125 0 : }
126 :
127 0 : void SkBaseDevice::drawPatch(const SkPoint cubics[12], const SkColor colors[4],
128 : const SkPoint texCoords[4], SkBlendMode bmode, const SkPaint& paint) {
129 0 : SkISize lod = SkPatchUtils::GetLevelOfDetail(cubics, &this->ctm());
130 0 : auto vertices = SkPatchUtils::MakeVertices(cubics, colors, texCoords, lod.width(), lod.height());
131 0 : if (vertices) {
132 0 : this->drawVertices(vertices.get(), bmode, paint);
133 : }
134 0 : }
135 :
136 0 : void SkBaseDevice::drawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
137 : const SkPaint &paint, SkDrawFilter* drawFilter) {
138 :
139 0 : SkPaint runPaint = paint;
140 :
141 0 : SkTextBlobRunIterator it(blob);
142 0 : for (;!it.done(); it.next()) {
143 0 : size_t textLen = it.glyphCount() * sizeof(uint16_t);
144 0 : const SkPoint& offset = it.offset();
145 : // applyFontToPaint() always overwrites the exact same attributes,
146 : // so it is safe to not re-seed the paint for this reason.
147 0 : it.applyFontToPaint(&runPaint);
148 :
149 0 : if (drawFilter && !drawFilter->filter(&runPaint, SkDrawFilter::kText_Type)) {
150 : // A false return from filter() means we should abort the current draw.
151 0 : runPaint = paint;
152 0 : continue;
153 : }
154 :
155 0 : runPaint.setFlags(this->filterTextFlags(runPaint));
156 :
157 0 : switch (it.positioning()) {
158 : case SkTextBlob::kDefault_Positioning:
159 0 : this->drawText(it.glyphs(), textLen, x + offset.x(), y + offset.y(), runPaint);
160 0 : break;
161 : case SkTextBlob::kHorizontal_Positioning:
162 0 : this->drawPosText(it.glyphs(), textLen, it.pos(), 1,
163 0 : SkPoint::Make(x, y + offset.y()), runPaint);
164 0 : break;
165 : case SkTextBlob::kFull_Positioning:
166 0 : this->drawPosText(it.glyphs(), textLen, it.pos(), 2,
167 0 : SkPoint::Make(x, y), runPaint);
168 0 : break;
169 : default:
170 0 : SkFAIL("unhandled positioning mode");
171 : }
172 :
173 0 : if (drawFilter) {
174 : // A draw filter may change the paint arbitrarily, so we must re-seed in this case.
175 0 : runPaint = paint;
176 : }
177 : }
178 0 : }
179 :
180 4 : void SkBaseDevice::drawImage(const SkImage* image, SkScalar x, SkScalar y,
181 : const SkPaint& paint) {
182 8 : SkBitmap bm;
183 4 : if (as_IB(image)->getROPixels(&bm, this->imageInfo().colorSpace())) {
184 4 : this->drawBitmap(bm, SkMatrix::MakeTrans(x, y), paint);
185 : }
186 4 : }
187 :
188 140 : void SkBaseDevice::drawImageRect(const SkImage* image, const SkRect* src,
189 : const SkRect& dst, const SkPaint& paint,
190 : SkCanvas::SrcRectConstraint constraint) {
191 280 : SkBitmap bm;
192 140 : if (as_IB(image)->getROPixels(&bm, this->imageInfo().colorSpace())) {
193 140 : this->drawBitmapRect(bm, src, dst, paint, constraint);
194 : }
195 140 : }
196 :
197 0 : void SkBaseDevice::drawImageNine(const SkImage* image, const SkIRect& center,
198 : const SkRect& dst, const SkPaint& paint) {
199 0 : SkLatticeIter iter(image->width(), image->height(), center, dst);
200 :
201 : SkRect srcR, dstR;
202 0 : while (iter.next(&srcR, &dstR)) {
203 0 : this->drawImageRect(image, &srcR, dstR, paint, SkCanvas::kStrict_SrcRectConstraint);
204 : }
205 0 : }
206 :
207 0 : void SkBaseDevice::drawBitmapNine(const SkBitmap& bitmap, const SkIRect& center,
208 : const SkRect& dst, const SkPaint& paint) {
209 0 : SkLatticeIter iter(bitmap.width(), bitmap.height(), center, dst);
210 :
211 : SkRect srcR, dstR;
212 0 : while (iter.next(&srcR, &dstR)) {
213 0 : this->drawBitmapRect(bitmap, &srcR, dstR, paint, SkCanvas::kStrict_SrcRectConstraint);
214 : }
215 0 : }
216 :
217 0 : void SkBaseDevice::drawImageLattice(const SkImage* image,
218 : const SkCanvas::Lattice& lattice, const SkRect& dst,
219 : const SkPaint& paint) {
220 0 : SkLatticeIter iter(lattice, dst);
221 :
222 : SkRect srcR, dstR;
223 0 : while (iter.next(&srcR, &dstR)) {
224 0 : this->drawImageRect(image, &srcR, dstR, paint, SkCanvas::kStrict_SrcRectConstraint);
225 : }
226 0 : }
227 :
228 0 : void SkBaseDevice::drawBitmapLattice(const SkBitmap& bitmap,
229 : const SkCanvas::Lattice& lattice, const SkRect& dst,
230 : const SkPaint& paint) {
231 0 : SkLatticeIter iter(lattice, dst);
232 :
233 : SkRect srcR, dstR;
234 0 : while (iter.next(&srcR, &dstR)) {
235 0 : this->drawBitmapRect(bitmap, &srcR, dstR, paint, SkCanvas::kStrict_SrcRectConstraint);
236 : }
237 0 : }
238 :
239 0 : void SkBaseDevice::drawAtlas(const SkImage* atlas, const SkRSXform xform[],
240 : const SkRect tex[], const SkColor colors[], int count,
241 : SkBlendMode mode, const SkPaint& paint) {
242 0 : SkPath path;
243 0 : path.setIsVolatile(true);
244 :
245 0 : for (int i = 0; i < count; ++i) {
246 : SkPoint quad[4];
247 0 : xform[i].toQuad(tex[i].width(), tex[i].height(), quad);
248 :
249 : SkMatrix localM;
250 0 : localM.setRSXform(xform[i]);
251 0 : localM.preTranslate(-tex[i].left(), -tex[i].top());
252 :
253 0 : SkPaint pnt(paint);
254 : sk_sp<SkShader> shader = atlas->makeShader(SkShader::kClamp_TileMode,
255 : SkShader::kClamp_TileMode,
256 0 : &localM);
257 0 : if (!shader) {
258 0 : break;
259 : }
260 0 : pnt.setShader(std::move(shader));
261 :
262 0 : if (colors) {
263 0 : pnt.setColorFilter(SkColorFilter::MakeModeFilter(colors[i], (SkBlendMode)mode));
264 : }
265 :
266 0 : path.rewind();
267 0 : path.addPoly(quad, 4, true);
268 0 : path.setConvexity(SkPath::kConvex_Convexity);
269 0 : this->drawPath(path, pnt, nullptr, true);
270 : }
271 0 : }
272 :
273 : ///////////////////////////////////////////////////////////////////////////////////////////////////
274 :
275 0 : void SkBaseDevice::drawSpecial(SkSpecialImage*, int x, int y, const SkPaint&) {}
276 0 : sk_sp<SkSpecialImage> SkBaseDevice::makeSpecial(const SkBitmap&) { return nullptr; }
277 0 : sk_sp<SkSpecialImage> SkBaseDevice::makeSpecial(const SkImage*) { return nullptr; }
278 0 : sk_sp<SkSpecialImage> SkBaseDevice::snapSpecial() { return nullptr; }
279 :
280 : ///////////////////////////////////////////////////////////////////////////////////////////////////
281 :
282 0 : bool SkBaseDevice::readPixels(const SkImageInfo& info, void* dstP, size_t rowBytes, int x, int y) {
283 0 : return this->onReadPixels(info, dstP, rowBytes, x, y);
284 : }
285 :
286 0 : bool SkBaseDevice::writePixels(const SkImageInfo& info, const void* pixels, size_t rowBytes,
287 : int x, int y) {
288 0 : return this->onWritePixels(info, pixels, rowBytes, x, y);
289 : }
290 :
291 0 : bool SkBaseDevice::onWritePixels(const SkImageInfo&, const void*, size_t, int, int) {
292 0 : return false;
293 : }
294 :
295 0 : bool SkBaseDevice::onReadPixels(const SkImageInfo&, void*, size_t, int x, int y) {
296 0 : return false;
297 : }
298 :
299 473 : bool SkBaseDevice::accessPixels(SkPixmap* pmap) {
300 946 : SkPixmap tempStorage;
301 473 : if (nullptr == pmap) {
302 0 : pmap = &tempStorage;
303 : }
304 946 : return this->onAccessPixels(pmap);
305 : }
306 :
307 40 : bool SkBaseDevice::peekPixels(SkPixmap* pmap) {
308 80 : SkPixmap tempStorage;
309 40 : if (nullptr == pmap) {
310 0 : pmap = &tempStorage;
311 : }
312 80 : return this->onPeekPixels(pmap);
313 : }
314 :
315 : //////////////////////////////////////////////////////////////////////////////////////////
316 :
317 0 : static void morphpoints(SkPoint dst[], const SkPoint src[], int count,
318 : SkPathMeasure& meas, const SkMatrix& matrix) {
319 0 : SkMatrix::MapXYProc proc = matrix.getMapXYProc();
320 :
321 0 : for (int i = 0; i < count; i++) {
322 : SkPoint pos;
323 : SkVector tangent;
324 :
325 0 : proc(matrix, src[i].fX, src[i].fY, &pos);
326 0 : SkScalar sx = pos.fX;
327 0 : SkScalar sy = pos.fY;
328 :
329 0 : if (!meas.getPosTan(sx, &pos, &tangent)) {
330 : // set to 0 if the measure failed, so that we just set dst == pos
331 0 : tangent.set(0, 0);
332 : }
333 :
334 : /* This is the old way (that explains our approach but is way too slow
335 : SkMatrix matrix;
336 : SkPoint pt;
337 :
338 : pt.set(sx, sy);
339 : matrix.setSinCos(tangent.fY, tangent.fX);
340 : matrix.preTranslate(-sx, 0);
341 : matrix.postTranslate(pos.fX, pos.fY);
342 : matrix.mapPoints(&dst[i], &pt, 1);
343 : */
344 0 : dst[i].set(pos.fX - tangent.fY * sy, pos.fY + tangent.fX * sy);
345 : }
346 0 : }
347 :
348 : /* TODO
349 :
350 : Need differentially more subdivisions when the follow-path is curvy. Not sure how to
351 : determine that, but we need it. I guess a cheap answer is let the caller tell us,
352 : but that seems like a cop-out. Another answer is to get Rob Johnson to figure it out.
353 : */
354 0 : static void morphpath(SkPath* dst, const SkPath& src, SkPathMeasure& meas,
355 : const SkMatrix& matrix) {
356 0 : SkPath::Iter iter(src, false);
357 : SkPoint srcP[4], dstP[3];
358 : SkPath::Verb verb;
359 :
360 0 : while ((verb = iter.next(srcP)) != SkPath::kDone_Verb) {
361 0 : switch (verb) {
362 : case SkPath::kMove_Verb:
363 0 : morphpoints(dstP, srcP, 1, meas, matrix);
364 0 : dst->moveTo(dstP[0]);
365 0 : break;
366 : case SkPath::kLine_Verb:
367 : // turn lines into quads to look bendy
368 0 : srcP[0].fX = SkScalarAve(srcP[0].fX, srcP[1].fX);
369 0 : srcP[0].fY = SkScalarAve(srcP[0].fY, srcP[1].fY);
370 0 : morphpoints(dstP, srcP, 2, meas, matrix);
371 0 : dst->quadTo(dstP[0], dstP[1]);
372 0 : break;
373 : case SkPath::kQuad_Verb:
374 0 : morphpoints(dstP, &srcP[1], 2, meas, matrix);
375 0 : dst->quadTo(dstP[0], dstP[1]);
376 0 : break;
377 : case SkPath::kCubic_Verb:
378 0 : morphpoints(dstP, &srcP[1], 3, meas, matrix);
379 0 : dst->cubicTo(dstP[0], dstP[1], dstP[2]);
380 0 : break;
381 : case SkPath::kClose_Verb:
382 0 : dst->close();
383 0 : break;
384 : default:
385 0 : SkDEBUGFAIL("unknown verb");
386 0 : break;
387 : }
388 : }
389 0 : }
390 :
391 0 : void SkBaseDevice::drawTextOnPath(const void* text, size_t byteLength,
392 : const SkPath& follow, const SkMatrix* matrix,
393 : const SkPaint& paint) {
394 0 : SkASSERT(byteLength == 0 || text != nullptr);
395 :
396 : // nothing to draw
397 0 : if (text == nullptr || byteLength == 0) {
398 0 : return;
399 : }
400 :
401 0 : SkTextToPathIter iter((const char*)text, byteLength, paint, true);
402 0 : SkPathMeasure meas(follow, false);
403 0 : SkScalar hOffset = 0;
404 :
405 : // need to measure first
406 0 : if (paint.getTextAlign() != SkPaint::kLeft_Align) {
407 0 : SkScalar pathLen = meas.getLength();
408 0 : if (paint.getTextAlign() == SkPaint::kCenter_Align) {
409 0 : pathLen = SkScalarHalf(pathLen);
410 : }
411 0 : hOffset += pathLen;
412 : }
413 :
414 : const SkPath* iterPath;
415 : SkScalar xpos;
416 : SkMatrix scaledMatrix;
417 0 : SkScalar scale = iter.getPathScale();
418 :
419 0 : scaledMatrix.setScale(scale, scale);
420 :
421 0 : while (iter.next(&iterPath, &xpos)) {
422 0 : if (iterPath) {
423 0 : SkPath tmp;
424 0 : SkMatrix m(scaledMatrix);
425 :
426 0 : tmp.setIsVolatile(true);
427 0 : m.postTranslate(xpos + hOffset, 0);
428 0 : if (matrix) {
429 0 : m.postConcat(*matrix);
430 : }
431 0 : morphpath(&tmp, *iterPath, meas, m);
432 0 : this->drawPath(tmp, iter.getPaint(), nullptr, true);
433 : }
434 : }
435 : }
436 :
437 : #include "SkUtils.h"
438 : typedef int (*CountTextProc)(const char* text);
439 0 : static int count_utf16(const char* text) {
440 0 : const uint16_t* prev = (uint16_t*)text;
441 0 : (void)SkUTF16_NextUnichar(&prev);
442 0 : return SkToInt((const char*)prev - text);
443 : }
444 0 : static int return_4(const char* text) { return 4; }
445 0 : static int return_2(const char* text) { return 2; }
446 :
447 0 : void SkBaseDevice::drawTextRSXform(const void* text, size_t len,
448 : const SkRSXform xform[], const SkPaint& paint) {
449 0 : CountTextProc proc = nullptr;
450 0 : switch (paint.getTextEncoding()) {
451 : case SkPaint::kUTF8_TextEncoding:
452 0 : proc = SkUTF8_CountUTF8Bytes;
453 0 : break;
454 : case SkPaint::kUTF16_TextEncoding:
455 0 : proc = count_utf16;
456 0 : break;
457 : case SkPaint::kUTF32_TextEncoding:
458 0 : proc = return_4;
459 0 : break;
460 : case SkPaint::kGlyphID_TextEncoding:
461 0 : proc = return_2;
462 0 : break;
463 : }
464 :
465 : SkMatrix localM, currM;
466 0 : const void* stopText = (const char*)text + len;
467 0 : while ((const char*)text < (const char*)stopText) {
468 0 : localM.setRSXform(*xform++);
469 0 : currM.setConcat(this->ctm(), localM);
470 0 : SkAutoDeviceCTMRestore adc(this, currM);
471 :
472 0 : int subLen = proc((const char*)text);
473 0 : this->drawText(text, subLen, 0, 0, paint);
474 0 : text = (const char*)text + subLen;
475 : }
476 0 : }
477 :
478 : //////////////////////////////////////////////////////////////////////////////////////////
479 :
480 21 : uint32_t SkBaseDevice::filterTextFlags(const SkPaint& paint) const {
481 21 : uint32_t flags = paint.getFlags();
482 :
483 21 : if (!paint.isLCDRenderText() || !paint.isAntiAlias()) {
484 0 : return flags;
485 : }
486 :
487 42 : if (kUnknown_SkPixelGeometry == fSurfaceProps.pixelGeometry()
488 21 : || this->onShouldDisableLCD(paint)) {
489 :
490 0 : flags &= ~SkPaint::kLCDRenderText_Flag;
491 0 : flags |= SkPaint::kGenA8FromLCD_Flag;
492 : }
493 :
494 21 : return flags;
495 : }
496 :
497 0 : sk_sp<SkSurface> SkBaseDevice::makeSurface(SkImageInfo const&, SkSurfaceProps const&) {
498 0 : return nullptr;
499 : }
500 :
501 : //////////////////////////////////////////////////////////////////////////////////////////
502 :
503 144 : void SkBaseDevice::LogDrawScaleFactor(const SkMatrix& matrix, SkFilterQuality filterQuality) {
504 : #if SK_HISTOGRAMS_ENABLED
505 : enum ScaleFactor {
506 : kUpscale_ScaleFactor,
507 : kNoScale_ScaleFactor,
508 : kDownscale_ScaleFactor,
509 : kLargeDownscale_ScaleFactor,
510 :
511 : kLast_ScaleFactor = kLargeDownscale_ScaleFactor
512 : };
513 :
514 : float rawScaleFactor = matrix.getMinScale();
515 :
516 : ScaleFactor scaleFactor;
517 : if (rawScaleFactor < 0.5f) {
518 : scaleFactor = kLargeDownscale_ScaleFactor;
519 : } else if (rawScaleFactor < 1.0f) {
520 : scaleFactor = kDownscale_ScaleFactor;
521 : } else if (rawScaleFactor > 1.0f) {
522 : scaleFactor = kUpscale_ScaleFactor;
523 : } else {
524 : scaleFactor = kNoScale_ScaleFactor;
525 : }
526 :
527 : switch (filterQuality) {
528 : case kNone_SkFilterQuality:
529 : SK_HISTOGRAM_ENUMERATION("DrawScaleFactor.NoneFilterQuality", scaleFactor,
530 : kLast_ScaleFactor + 1);
531 : break;
532 : case kLow_SkFilterQuality:
533 : SK_HISTOGRAM_ENUMERATION("DrawScaleFactor.LowFilterQuality", scaleFactor,
534 : kLast_ScaleFactor + 1);
535 : break;
536 : case kMedium_SkFilterQuality:
537 : SK_HISTOGRAM_ENUMERATION("DrawScaleFactor.MediumFilterQuality", scaleFactor,
538 : kLast_ScaleFactor + 1);
539 : break;
540 : case kHigh_SkFilterQuality:
541 : SK_HISTOGRAM_ENUMERATION("DrawScaleFactor.HighFilterQuality", scaleFactor,
542 : kLast_ScaleFactor + 1);
543 : break;
544 : }
545 :
546 : // Also log filter quality independent scale factor.
547 : SK_HISTOGRAM_ENUMERATION("DrawScaleFactor.AnyFilterQuality", scaleFactor,
548 : kLast_ScaleFactor + 1);
549 :
550 : // Also log an overall histogram of filter quality.
551 : SK_HISTOGRAM_ENUMERATION("FilterQuality", filterQuality, kLast_SkFilterQuality + 1);
552 : #endif
553 144 : }
554 :
|