1 /* 2 * Copyright 2015 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 "gm/gm.h" 9 #include "include/core/SkBlendMode.h" 10 #include "include/core/SkCanvas.h" 11 #include "include/core/SkColor.h" 12 #include "include/core/SkFont.h" 13 #include "include/core/SkPaint.h" 14 #include "include/core/SkPathBuilder.h" 15 #include "include/core/SkPoint.h" 16 #include "include/core/SkRect.h" 17 #include "include/core/SkScalar.h" 18 #include "include/core/SkSize.h" 19 #include "include/core/SkString.h" 20 #include "include/core/SkTypeface.h" 21 #include "include/core/SkTypes.h" 22 #include "include/utils/SkTextUtils.h" 23 #include "tools/ToolUtils.h" 24 25 enum { 26 kXfermodeCount = (int)SkBlendMode::kLastMode + 1 + 1, // extra for arith 27 kShapeSize = 22, 28 kShapeSpacing = 36, 29 kShapeTypeSpacing = 4 * kShapeSpacing / 3, 30 kPaintSpacing = 4 * kShapeTypeSpacing, 31 kLabelSpacing = 3 * kShapeSize, 32 kMargin = kShapeSpacing / 2, 33 kXfermodeTypeSpacing = kLabelSpacing + 2 * kPaintSpacing + kShapeTypeSpacing, 34 kTitleSpacing = 3 * kShapeSpacing / 4, 35 kSubtitleSpacing = 5 * kShapeSpacing / 8 36 }; 37 38 constexpr SkColor kBGColor = 0xc8d2b887; 39 40 constexpr SkColor kShapeColors[2] = { 41 0x82ff0080, // input color unknown 42 0xff00ffff, // input color opaque 43 }; 44 45 enum Shape { 46 kSquare_Shape, 47 kDiamond_Shape, 48 kOval_Shape, 49 kConcave_Shape, 50 51 kLast_Shape = kConcave_Shape 52 }; 53 54 /** 55 * Verifies AA works properly on all Xfermodes, including arithmetic, with both opaque and unknown 56 * src colors. 57 */ 58 class AAXfermodesGM : public skiagm::GM { 59 public: AAXfermodesGM()60 AAXfermodesGM() {} 61 62 protected: 63 enum DrawingPass { 64 kCheckerboard_Pass, 65 kBackground_Pass, 66 kShape_Pass 67 }; 68 onShortName()69 SkString onShortName() override { 70 return SkString("aaxfermodes"); 71 } 72 onISize()73 SkISize onISize() override { 74 return SkISize::Make(2 * kMargin + 2 * kXfermodeTypeSpacing - 75 (kXfermodeTypeSpacing - (kLabelSpacing + 2 * kPaintSpacing)), 76 2 * kMargin + kTitleSpacing + kSubtitleSpacing + 77 (1 + (int)SkBlendMode::kLastCoeffMode) * kShapeSpacing); 78 } 79 onOnceBeforeDraw()80 void onOnceBeforeDraw() override { 81 fLabelFont.setTypeface(ToolUtils::create_portable_typeface()); 82 fLabelFont.setSize(5 * kShapeSize/8); 83 fLabelFont.setSubpixel(true); 84 85 constexpr SkScalar radius = -1.4f * kShapeSize/2; 86 SkPoint pts[4] = { 87 {-radius, 0}, 88 {0, -1.33f * radius}, 89 {radius, 0}, 90 {0, 1.33f * radius} 91 }; 92 fOval = SkPathBuilder().moveTo(pts[0]) 93 .quadTo(pts[1], pts[2]) 94 .quadTo(pts[3], pts[0]) 95 .detach(); 96 97 fConcave = SkPathBuilder().moveTo(-radius, 0) 98 .quadTo(0, 0, 0, -radius) 99 .quadTo(0, 0, radius, 0) 100 .quadTo(0, 0, 0, radius) 101 .quadTo(0, 0, -radius, 0) 102 .close() 103 .detach(); 104 } 105 draw_pass(SkCanvas * canvas,DrawingPass drawingPass)106 void draw_pass(SkCanvas* canvas, DrawingPass drawingPass) { 107 SkRect clipRect = 108 { -kShapeSize*11/16, -kShapeSize*11/16, kShapeSize*11/16, kShapeSize*11/16 }; 109 110 canvas->save(); 111 if (kCheckerboard_Pass == drawingPass) { 112 canvas->translate(kMargin, kMargin); 113 } 114 canvas->translate(0, kTitleSpacing); 115 116 for (size_t xfermodeSet = 0; xfermodeSet < 2; xfermodeSet++) { 117 size_t firstMode = ((size_t)SkBlendMode::kLastCoeffMode + 1) * xfermodeSet; 118 canvas->save(); 119 120 if (kShape_Pass == drawingPass) { 121 SkTextUtils::DrawString(canvas, "Src Unknown", 122 kLabelSpacing + kShapeTypeSpacing * 1.5f + kShapeSpacing / 2, 123 kSubtitleSpacing / 2 + fLabelFont.getSize() / 3, fLabelFont, SkPaint(), 124 SkTextUtils::kCenter_Align); 125 SkTextUtils::DrawString(canvas, "Src Opaque", 126 kLabelSpacing + kShapeTypeSpacing * 1.5f + kShapeSpacing / 2 + 127 kPaintSpacing, kSubtitleSpacing / 2 + fLabelFont.getSize() / 3, 128 fLabelFont, SkPaint(), SkTextUtils::kCenter_Align); 129 } 130 131 canvas->translate(0, kSubtitleSpacing + kShapeSpacing/2); 132 133 for (size_t m = 0; m <= (size_t)SkBlendMode::kLastCoeffMode; m++) { 134 if (firstMode + m > (size_t)SkBlendMode::kLastMode) { 135 break; 136 } 137 SkBlendMode mode = static_cast<SkBlendMode>(firstMode + m); 138 canvas->save(); 139 140 if (kShape_Pass == drawingPass) { 141 this->drawModeName(canvas, mode); 142 } 143 canvas->translate(kLabelSpacing + kShapeSpacing/2, 0); 144 145 for (size_t colorIdx = 0; colorIdx < SK_ARRAY_COUNT(kShapeColors); colorIdx++) { 146 SkPaint paint; 147 this->setupShapePaint(canvas, kShapeColors[colorIdx], mode, &paint); 148 SkASSERT(colorIdx == 0 || 255 == paint.getAlpha()); 149 canvas->save(); 150 151 for (size_t shapeIdx = 0; shapeIdx <= kLast_Shape; shapeIdx++) { 152 if (kShape_Pass != drawingPass) { 153 canvas->save(); 154 canvas->clipRect(clipRect); 155 if (kCheckerboard_Pass == drawingPass) { 156 ToolUtils::draw_checkerboard(canvas, 0xffffffff, 0xffc6c3c6, 10); 157 } else { 158 SkASSERT(kBackground_Pass == drawingPass); 159 canvas->drawColor(kBGColor, SkBlendMode::kSrc); 160 } 161 canvas->restore(); 162 } else { 163 this->drawShape(canvas, static_cast<Shape>(shapeIdx), paint, mode); 164 } 165 canvas->translate(kShapeTypeSpacing, 0); 166 } 167 168 canvas->restore(); 169 canvas->translate(kPaintSpacing, 0); 170 } 171 172 canvas->restore(); 173 canvas->translate(0, kShapeSpacing); 174 } 175 176 canvas->restore(); 177 canvas->translate(kXfermodeTypeSpacing, 0); 178 } 179 180 canvas->restore(); 181 } 182 onDraw(SkCanvas * canvas)183 void onDraw(SkCanvas* canvas) override { 184 draw_pass(canvas, kCheckerboard_Pass); 185 canvas->saveLayer(nullptr, nullptr); 186 187 canvas->translate(kMargin, kMargin); 188 draw_pass(canvas, kBackground_Pass); 189 190 SkFont titleFont(fLabelFont); 191 titleFont.setSize(9 * titleFont.getSize() / 8); 192 titleFont.setEmbolden(true); 193 SkTextUtils::DrawString(canvas, "Porter Duff", 194 kLabelSpacing + 4 * kShapeTypeSpacing, 195 kTitleSpacing / 2 + titleFont.getSize() / 3, titleFont, SkPaint(), 196 SkTextUtils::kCenter_Align); 197 SkTextUtils::DrawString(canvas, "Advanced", 198 kXfermodeTypeSpacing + kLabelSpacing + 4 * kShapeTypeSpacing, 199 kTitleSpacing / 2 + titleFont.getSize() / 3, titleFont, SkPaint(), 200 SkTextUtils::kCenter_Align); 201 202 draw_pass(canvas, kShape_Pass); 203 canvas->restore(); 204 } 205 drawModeName(SkCanvas * canvas,SkBlendMode mode)206 void drawModeName(SkCanvas* canvas, SkBlendMode mode) { 207 const char* modeName = SkBlendMode_Name(mode); 208 SkTextUtils::DrawString(canvas, modeName, kLabelSpacing - kShapeSize / 4, 209 fLabelFont.getSize() / 4, fLabelFont, SkPaint(), 210 SkTextUtils::kRight_Align); 211 } 212 setupShapePaint(SkCanvas * canvas,SkColor color,SkBlendMode mode,SkPaint * paint)213 void setupShapePaint(SkCanvas* canvas, SkColor color, SkBlendMode mode, SkPaint* paint) { 214 paint->setColor(color); 215 216 if (mode == SkBlendMode::kPlus) { 217 // Check for overflow, otherwise we might get confusing AA artifacts. 218 int maxSum = std::max(std::max(SkColorGetA(kBGColor) + SkColorGetA(color), 219 SkColorGetR(kBGColor) + SkColorGetR(color)), 220 std::max(SkColorGetG(kBGColor) + SkColorGetG(color), 221 SkColorGetB(kBGColor) + SkColorGetB(color))); 222 223 if (maxSum > 255) { 224 SkPaint dimPaint; 225 dimPaint.setAntiAlias(false); 226 dimPaint.setBlendMode(SkBlendMode::kDstIn); 227 if (255 != paint->getAlpha()) { 228 // Dim the src and dst colors. 229 dimPaint.setARGB(255 * 255 / maxSum, 0, 0, 0); 230 paint->setAlpha(255 * paint->getAlpha() / maxSum); 231 } else { 232 // Just clear the dst, we need to preserve the paint's opacity. 233 dimPaint.setARGB(0, 0, 0, 0); 234 } 235 canvas->drawRect({ -kShapeSpacing/2, -kShapeSpacing/2, 236 kShapeSpacing/2 + 3 * kShapeTypeSpacing, kShapeSpacing/2 }, 237 dimPaint); 238 } 239 } 240 } 241 drawShape(SkCanvas * canvas,Shape shape,const SkPaint & paint,SkBlendMode mode)242 void drawShape(SkCanvas* canvas, Shape shape, const SkPaint& paint, SkBlendMode mode) { 243 SkASSERT(mode <= SkBlendMode::kLastMode); 244 SkPaint shapePaint(paint); 245 shapePaint.setAntiAlias(kSquare_Shape != shape); 246 shapePaint.setBlendMode(mode); 247 248 switch (shape) { 249 case kSquare_Shape: 250 canvas->drawRect({ -kShapeSize/2, -kShapeSize/2, kShapeSize/2, kShapeSize/2 }, 251 shapePaint); 252 break; 253 254 case kDiamond_Shape: 255 canvas->save(); 256 canvas->rotate(45); 257 canvas->drawRect({ -kShapeSize/2, -kShapeSize/2, kShapeSize/2, kShapeSize/2 }, 258 shapePaint); 259 canvas->restore(); 260 break; 261 262 case kOval_Shape: 263 canvas->save(); 264 canvas->rotate(static_cast<SkScalar>((511 * (int)mode + 257) % 360)); 265 canvas->drawPath(fOval, shapePaint); 266 canvas->restore(); 267 break; 268 269 case kConcave_Shape: 270 canvas->drawPath(fConcave, shapePaint); 271 break; 272 273 default: 274 SK_ABORT("Invalid shape."); 275 } 276 } 277 278 private: 279 SkFont fLabelFont; 280 SkPath fOval; 281 SkPath fConcave; 282 283 using INHERITED = skiagm::GM; 284 }; 285 DEF_GM( return new AAXfermodesGM; ) 286