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