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/SkPath.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.moveTo(pts[0]);
93         fOval.quadTo(pts[1], pts[2]);
94         fOval.quadTo(pts[3], pts[0]);
95 
96         fConcave.moveTo(-radius, 0);
97         fConcave.quadTo(0, 0, 0, -radius);
98         fConcave.quadTo(0, 0, radius, 0);
99         fConcave.quadTo(0, 0, 0, radius);
100         fConcave.quadTo(0, 0, -radius, 0);
101         fConcave.close();
102     }
103 
draw_pass(SkCanvas * canvas,DrawingPass drawingPass)104     void draw_pass(SkCanvas* canvas, DrawingPass drawingPass) {
105         SkRect clipRect =
106                 { -kShapeSize*11/16, -kShapeSize*11/16, kShapeSize*11/16, kShapeSize*11/16 };
107 
108         canvas->save();
109         if (kCheckerboard_Pass == drawingPass) {
110             canvas->translate(kMargin, kMargin);
111         }
112         canvas->translate(0, kTitleSpacing);
113 
114         for (size_t xfermodeSet = 0; xfermodeSet < 2; xfermodeSet++) {
115             size_t firstMode = ((size_t)SkBlendMode::kLastCoeffMode + 1) * xfermodeSet;
116             canvas->save();
117 
118             if (kShape_Pass == drawingPass) {
119                 SkTextUtils::DrawString(canvas, "Src Unknown",
120                         kLabelSpacing + kShapeTypeSpacing * 1.5f + kShapeSpacing / 2,
121                         kSubtitleSpacing / 2 + fLabelFont.getSize() / 3, fLabelFont, SkPaint(),
122                                         SkTextUtils::kCenter_Align);
123                 SkTextUtils::DrawString(canvas, "Src Opaque",
124                         kLabelSpacing + kShapeTypeSpacing * 1.5f + kShapeSpacing / 2 +
125                         kPaintSpacing, kSubtitleSpacing / 2 + fLabelFont.getSize() / 3,
126                                         fLabelFont, SkPaint(), SkTextUtils::kCenter_Align);
127             }
128 
129             canvas->translate(0, kSubtitleSpacing + kShapeSpacing/2);
130 
131             for (size_t m = 0; m <= (size_t)SkBlendMode::kLastCoeffMode; m++) {
132                 if (firstMode + m > (size_t)SkBlendMode::kLastMode) {
133                     break;
134                 }
135                 SkBlendMode mode = static_cast<SkBlendMode>(firstMode + m);
136                 canvas->save();
137 
138                 if (kShape_Pass == drawingPass) {
139                     this->drawModeName(canvas, mode);
140                 }
141                 canvas->translate(kLabelSpacing + kShapeSpacing/2, 0);
142 
143                 for (size_t colorIdx = 0; colorIdx < SK_ARRAY_COUNT(kShapeColors); colorIdx++) {
144                     SkPaint paint;
145                     this->setupShapePaint(canvas, kShapeColors[colorIdx], mode, &paint);
146                     SkASSERT(colorIdx == 0 || 255 == paint.getAlpha());
147                     canvas->save();
148 
149                     for (size_t shapeIdx = 0; shapeIdx <= kLast_Shape; shapeIdx++) {
150                         if (kShape_Pass != drawingPass) {
151                             canvas->save();
152                             canvas->clipRect(clipRect);
153                             if (kCheckerboard_Pass == drawingPass) {
154                                 ToolUtils::draw_checkerboard(canvas, 0xffffffff, 0xffc6c3c6, 10);
155                             } else {
156                                 SkASSERT(kBackground_Pass == drawingPass);
157                                 canvas->drawColor(kBGColor, SkBlendMode::kSrc);
158                             }
159                             canvas->restore();
160                         } else {
161                             this->drawShape(canvas, static_cast<Shape>(shapeIdx), paint, mode);
162                         }
163                         canvas->translate(kShapeTypeSpacing, 0);
164                     }
165 
166                     canvas->restore();
167                     canvas->translate(kPaintSpacing, 0);
168                 }
169 
170                 canvas->restore();
171                 canvas->translate(0, kShapeSpacing);
172             }
173 
174             canvas->restore();
175             canvas->translate(kXfermodeTypeSpacing, 0);
176         }
177 
178         canvas->restore();
179     }
180 
onDraw(SkCanvas * canvas)181     void onDraw(SkCanvas* canvas) override {
182         draw_pass(canvas, kCheckerboard_Pass);
183         canvas->saveLayer(nullptr, nullptr);
184 
185         canvas->translate(kMargin, kMargin);
186         draw_pass(canvas, kBackground_Pass);
187 
188         SkFont titleFont(fLabelFont);
189         titleFont.setSize(9 * titleFont.getSize() / 8);
190         titleFont.setEmbolden(true);
191         SkTextUtils::DrawString(canvas, "Porter Duff",
192                                 kLabelSpacing + 4 * kShapeTypeSpacing,
193                                 kTitleSpacing / 2 + titleFont.getSize() / 3, titleFont, SkPaint(),
194                                 SkTextUtils::kCenter_Align);
195         SkTextUtils::DrawString(canvas, "Advanced",
196                                 kXfermodeTypeSpacing + kLabelSpacing + 4 * kShapeTypeSpacing,
197                                 kTitleSpacing / 2 + titleFont.getSize() / 3, titleFont, SkPaint(),
198                                 SkTextUtils::kCenter_Align);
199 
200         draw_pass(canvas, kShape_Pass);
201         canvas->restore();
202     }
203 
drawModeName(SkCanvas * canvas,SkBlendMode mode)204     void drawModeName(SkCanvas* canvas, SkBlendMode mode) {
205         const char* modeName = SkBlendMode_Name(mode);
206         SkTextUtils::DrawString(canvas, modeName, kLabelSpacing - kShapeSize / 4,
207                                 fLabelFont.getSize() / 4, fLabelFont, SkPaint(),
208                                 SkTextUtils::kRight_Align);
209     }
210 
setupShapePaint(SkCanvas * canvas,SkColor color,SkBlendMode mode,SkPaint * paint)211     void setupShapePaint(SkCanvas* canvas, SkColor color, SkBlendMode mode, SkPaint* paint) {
212         paint->setColor(color);
213 
214         if (mode == SkBlendMode::kPlus) {
215             // Check for overflow, otherwise we might get confusing AA artifacts.
216             int maxSum = std::max(std::max(SkColorGetA(kBGColor) + SkColorGetA(color),
217                                        SkColorGetR(kBGColor) + SkColorGetR(color)),
218                                 std::max(SkColorGetG(kBGColor) + SkColorGetG(color),
219                                        SkColorGetB(kBGColor) + SkColorGetB(color)));
220 
221             if (maxSum > 255) {
222                 SkPaint dimPaint;
223                 dimPaint.setAntiAlias(false);
224                 dimPaint.setBlendMode(SkBlendMode::kDstIn);
225                 if (255 != paint->getAlpha()) {
226                     // Dim the src and dst colors.
227                     dimPaint.setARGB(255 * 255 / maxSum, 0, 0, 0);
228                     paint->setAlpha(255 * paint->getAlpha() / maxSum);
229                 } else {
230                     // Just clear the dst, we need to preserve the paint's opacity.
231                     dimPaint.setARGB(0, 0, 0, 0);
232                 }
233                 canvas->drawRect({ -kShapeSpacing/2, -kShapeSpacing/2,
234                                    kShapeSpacing/2 + 3 * kShapeTypeSpacing, kShapeSpacing/2 },
235                                  dimPaint);
236             }
237         }
238     }
239 
drawShape(SkCanvas * canvas,Shape shape,const SkPaint & paint,SkBlendMode mode)240     void drawShape(SkCanvas* canvas, Shape shape, const SkPaint& paint, SkBlendMode mode) {
241         SkASSERT(mode <= SkBlendMode::kLastMode);
242         SkPaint shapePaint(paint);
243         shapePaint.setAntiAlias(kSquare_Shape != shape);
244         shapePaint.setBlendMode(mode);
245 
246         switch (shape) {
247             case kSquare_Shape:
248                 canvas->drawRect({ -kShapeSize/2, -kShapeSize/2, kShapeSize/2, kShapeSize/2 },
249                                  shapePaint);
250                 break;
251 
252             case kDiamond_Shape:
253                 canvas->save();
254                 canvas->rotate(45);
255                 canvas->drawRect({ -kShapeSize/2, -kShapeSize/2, kShapeSize/2, kShapeSize/2 },
256                                  shapePaint);
257                 canvas->restore();
258                 break;
259 
260             case kOval_Shape:
261                 canvas->save();
262                 canvas->rotate(static_cast<SkScalar>((511 * (int)mode + 257) % 360));
263                 canvas->drawPath(fOval, shapePaint);
264                 canvas->restore();
265                 break;
266 
267             case kConcave_Shape:
268                 canvas->drawPath(fConcave, shapePaint);
269                 break;
270 
271             default:
272                 SK_ABORT("Invalid shape.");
273         }
274     }
275 
276 private:
277     SkFont    fLabelFont;
278     SkPath    fOval;
279     SkPath    fConcave;
280 
281     typedef skiagm::GM INHERITED;
282 };
283 DEF_GM( return new AAXfermodesGM; )
284