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