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