1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.uirendering.cts.testclasses;
18 
19 import static org.junit.Assert.assertNotNull;
20 
21 import android.content.pm.PackageManager;
22 import android.graphics.Color;
23 import android.graphics.Paint;
24 import android.graphics.Path;
25 import android.graphics.Point;
26 import android.graphics.Typeface;
27 import android.uirendering.cts.R;
28 import android.uirendering.cts.bitmapcomparers.MSSIMComparer;
29 import android.uirendering.cts.bitmapverifiers.GoldenImageVerifier;
30 import android.uirendering.cts.bitmapverifiers.SamplePointVerifier;
31 import android.uirendering.cts.testinfrastructure.ActivityTestBase;
32 import android.uirendering.cts.testinfrastructure.CanvasClient;
33 import android.uirendering.cts.testinfrastructure.CanvasClientDrawable;
34 import android.uirendering.cts.testinfrastructure.ViewInitializer;
35 import android.uirendering.cts.util.WebViewReadyHelper;
36 import android.view.View;
37 import android.view.ViewGroup;
38 import android.webkit.WebView;
39 
40 import androidx.test.filters.LargeTest;
41 import androidx.test.filters.MediumTest;
42 import androidx.test.runner.AndroidJUnit4;
43 
44 import org.junit.Test;
45 import org.junit.runner.RunWith;
46 
47 import java.util.concurrent.CountDownLatch;
48 
49 @MediumTest
50 @RunWith(AndroidJUnit4.class)
51 public class PathClippingTests extends ActivityTestBase {
52     private static final String BLUE_RED_HTML =
53             "<html><head>"
54             + "    <style type=\"text/css\">"
55             + "        .container {"
56             + "            display: grid;"
57             + "            grid-template-columns: 50% 50%;"
58             + "            grid-template-rows: 100%;"
59             + "            margin: 0;"
60             + "        }"
61             + "    </style>"
62             + "</head><body class=\"container\">"
63             + "    <div style=\"background-color:blue\"></div>"
64             + "    <div style=\"background-color:red\"></div>"
65             + "</body></html>";
66 
67 
68     // draw circle with hole in it, with stroked circle
69     static final CanvasClient sTorusDrawCanvasClient = (canvas, width, height) -> {
70         Paint paint = new Paint();
71         paint.setAntiAlias(false);
72         paint.setColor(Color.BLUE);
73         paint.setStyle(Paint.Style.STROKE);
74         paint.setStrokeWidth(20);
75         canvas.drawCircle(30, 30, 40, paint);
76     };
77 
78     // draw circle with hole in it, by path operations + path clipping
79     static final CanvasClient sTorusClipCanvasClient = (canvas, width, height) -> {
80         canvas.save();
81 
82         Path path = new Path();
83         path.addCircle(30, 30, 50, Path.Direction.CW);
84         path.addCircle(30, 30, 30, Path.Direction.CCW);
85 
86         canvas.clipPath(path);
87         canvas.drawColor(Color.BLUE);
88 
89         canvas.restore();
90     };
91 
92     // draw circle with hole in it, by path operations + path clipping
93     static final CanvasClient sTorusClipOutCanvasClient = (canvas, width, height) -> {
94         canvas.save();
95 
96         Path path1 = new Path();
97         path1.addCircle(30, 30, 50, Path.Direction.CW);
98 
99         Path path2 = new Path();
100         path2.addCircle(30, 30, 30, Path.Direction.CW);
101 
102         canvas.clipPath(path1);
103         canvas.clipOutPath(path2);
104         canvas.drawColor(Color.BLUE);
105 
106         canvas.restore();
107     };
108 
109     @Test
testCircleWithCircle()110     public void testCircleWithCircle() {
111         createTest()
112                 .addCanvasClient("TorusDraw", sTorusDrawCanvasClient, false)
113                 .addCanvasClient("TorusClip", sTorusClipCanvasClient)
114                 .addCanvasClient("TorusClipOut", sTorusClipOutCanvasClient)
115                 .runWithVerifier(new GoldenImageVerifier(getActivity(),
116                         R.drawable.pathclippingtest_torus, new MSSIMComparer(0.65)));
117     }
118 
119     @Test
testCircleWithPoints()120     public void testCircleWithPoints() {
121         createTest()
122                 .addCanvasClient("TorusClip", sTorusClipCanvasClient)
123                 .addCanvasClient("TorusClipOut", sTorusClipOutCanvasClient)
124                 .runWithVerifier(new SamplePointVerifier(
125                         new Point[] {
126                                 // inside of circle
127                                 new Point(30, 50),
128                                 // on circle
129                                 new Point(30 + 32, 30 + 32),
130                                 // outside of circle
131                                 new Point(30 + 38, 30 + 38),
132                                 new Point(80, 80)
133                         },
134                         new int[] {
135                                 Color.WHITE,
136                                 Color.BLUE,
137                                 Color.WHITE,
138                                 Color.WHITE,
139                         }));
140     }
141 
142     @Test
testViewRotate()143     public void testViewRotate() {
144         createTest()
145                 .addLayout(R.layout.blue_padded_layout, view -> {
146                     ViewGroup rootView = (ViewGroup) view;
147                     rootView.setClipChildren(true);
148                     View childView = rootView.getChildAt(0);
149                     childView.setPivotX(40);
150                     childView.setPivotY(40);
151                     childView.setRotation(45f);
152                 })
153                 .runWithVerifier(new SamplePointVerifier(
154                         new Point[] {
155                                 // inside of rotated rect
156                                 new Point(40, 40),
157                                 new Point(40 + 25, 40 + 25),
158                                 // outside of rotated rect
159                                 new Point(40 + 31, 40 + 31),
160                                 new Point(80, 80)
161                         },
162                         new int[] {
163                                 Color.BLUE,
164                                 Color.BLUE,
165                                 Color.WHITE,
166                                 Color.WHITE,
167                         }));
168     }
169 
170     @Test
testPathScale()171     public void testPathScale() {
172         createTest()
173                 .addLayout(R.layout.frame_layout, view -> {
174                     Path path = new Path();
175                     path.addCircle(TEST_WIDTH / 2, TEST_HEIGHT / 2,
176                             TEST_WIDTH / 4, Path.Direction.CW);
177                     view.setBackground(new CanvasClientDrawable((canvas, width, height) -> {
178                         canvas.clipPath(path);
179                         canvas.drawColor(Color.BLUE);
180                     }));
181                     view.setScaleX(2);
182                     view.setScaleY(2);
183                 })
184                 .runWithComparer(new MSSIMComparer(0.87));
185     }
186 
187     @Test
testTextClip()188     public void testTextClip() {
189         createTest()
190                 .addCanvasClient((canvas, width, height) -> {
191                     canvas.save();
192 
193                     Path path = new Path();
194                     path.addCircle(0, 45, 45, Path.Direction.CW);
195                     path.addCircle(90, 45, 45, Path.Direction.CW);
196                     canvas.clipPath(path);
197 
198                     Paint paint = new Paint();
199                     paint.setAntiAlias(true);
200                     paint.setTextSize(90);
201                     paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
202                     canvas.drawText("STRING", 0, 90, paint);
203 
204                     canvas.restore();
205                 })
206                 .runWithComparer(new MSSIMComparer(0.90));
207     }
208 
initWebView(final CountDownLatch fence)209     private ViewInitializer initWebView(final CountDownLatch fence) {
210         return view -> {
211             WebView webview = (WebView)view.findViewById(R.id.webview);
212             assertNotNull(webview);
213             WebViewReadyHelper helper = new WebViewReadyHelper(webview, fence);
214             helper.loadData(BLUE_RED_HTML);
215         };
216     }
217 
218     @LargeTest
219     @Test
testWebViewClipWithCircle()220     public void testWebViewClipWithCircle() {
221         if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
222             return; // no WebView to run test on
223         }
224         CountDownLatch hwFence = new CountDownLatch(1);
225         CountDownLatch swFence = new CountDownLatch(1);
226         createTest()
227                 // golden client - draw a non-AA circle. left half is blue and right half is red.
228                 .addCanvasClient((canvas, width, height) -> {
229                     Paint paint = new Paint();
230                     paint.setAntiAlias(false);
231 
232                     int halfWidth = width / 2;
233 
234                     canvas.save();
235                     paint.setColor(Color.BLUE);
236                     canvas.clipRect(0, 0, halfWidth, height);
237                     canvas.drawOval(0, 0, width, height, paint);
238                     canvas.restore();
239 
240                     canvas.save();
241                     paint.setColor(Color.RED);
242                     canvas.clipRect(halfWidth, 0, width, height);
243                     canvas.drawOval(0, 0, width, height, paint);
244                     canvas.restore();
245                 }, false)
246                 // verify against webview drawing blue and red rects, clipped to its parent oval
247                 .addLayout(R.layout.circle_clipped_webview,
248                         initWebView(hwFence), true, hwFence)
249                 .addLayout(R.layout.circle_clipped_webview,
250                         initWebView(swFence), false, swFence)
251                 .runWithComparer(new MSSIMComparer(0.84f));
252     }
253 }
254