/* * Copyright (C) 2015, 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.view; import android.annotation.NonNull; import android.graphics.Canvas; import android.graphics.Outline; import android.graphics.Rect; import com.android.layoutlib.bridge.shadowutil.SpotShadow; import com.android.layoutlib.bridge.shadowutil.ShadowBuffer; public class RectShadowPainter { private static final float SHADOW_STRENGTH = 0.1f; private static final int LIGHT_POINTS = 8; private static final int QUADRANT_DIVIDED_COUNT = 8; private static final int RAY_TRACING_RAYS = 180; private static final int RAY_TRACING_LAYERS = 10; public static void paintShadow(@NonNull Outline viewOutline, float elevation, @NonNull Canvas canvas) { Rect outline = new Rect(); if (!viewOutline.getRect(outline)) { assert false : "Outline is not a rect shadow"; return; } Rect originCanvasRect = canvas.getClipBounds(); int saved = modifyCanvas(canvas); if (saved == -1) { return; } try { float radius = viewOutline.getRadius(); if (radius <= 0) { // We can not paint a shadow with radius 0 return; } // view's absolute position in this canvas. int viewLeft = -originCanvasRect.left + outline.left; int viewTop = -originCanvasRect.top + outline.top; int viewRight = viewLeft + outline.width(); int viewBottom = viewTop + outline.height(); float[][] rectangleCoordinators = generateRectangleCoordinates(viewLeft, viewTop, viewRight, viewBottom, radius, elevation); // TODO: get these values from resources. float lightPosX = canvas.getWidth() / 2; float lightPosY = 0; float lightHeight = 1800; float lightSize = 200; paintGeometricShadow(rectangleCoordinators, lightPosX, lightPosY, lightHeight, lightSize, canvas); } finally { canvas.restoreToCount(saved); } } private static int modifyCanvas(@NonNull Canvas canvas) { Rect rect = canvas.getClipBounds(); canvas.translate(rect.left, rect.top); return canvas.save(); } @NonNull private static float[][] generateRectangleCoordinates(float left, float top, float right, float bottom, float radius, float elevation) { left = left + radius; top = top + radius; right = right - radius; bottom = bottom - radius; final double RADIANS_STEP = 2 * Math.PI / 4 / QUADRANT_DIVIDED_COUNT; float[][] ret = new float[QUADRANT_DIVIDED_COUNT * 4][3]; int points = 0; // left-bottom points for (int i = 0; i < QUADRANT_DIVIDED_COUNT; i++) { ret[points][0] = (float) (left - radius + radius * Math.cos(RADIANS_STEP * i)); ret[points][1] = (float) (bottom + radius - radius * Math.cos(RADIANS_STEP * i)); ret[points][2] = elevation; points++; } // left-top points for (int i = 0; i < QUADRANT_DIVIDED_COUNT; i++) { ret[points][0] = (float) (left + radius - radius * Math.cos(RADIANS_STEP * i)); ret[points][1] = (float) (top + radius - radius * Math.cos(RADIANS_STEP * i)); ret[points][2] = elevation; points++; } // right-top points for (int i = 0; i < QUADRANT_DIVIDED_COUNT; i++) { ret[points][0] = (float) (right + radius - radius * Math.cos(RADIANS_STEP * i)); ret[points][1] = (float) (top + radius + radius * Math.cos(RADIANS_STEP * i)); ret[points][2] = elevation; points++; } // right-bottom point for (int i = 0; i < QUADRANT_DIVIDED_COUNT; i++) { ret[points][0] = (float) (right - radius + radius * Math.cos(RADIANS_STEP * i)); ret[points][1] = (float) (bottom - radius + radius * Math.cos(RADIANS_STEP * i)); ret[points][2] = elevation; points++; } return ret; } private static void paintGeometricShadow(@NonNull float[][] coordinates, float lightPosX, float lightPosY, float lightHeight, float lightSize, Canvas canvas) { if (canvas == null || canvas.getWidth() == 0 || canvas.getHeight() == 0) { return; } // The polygon of shadow (same as the original item) float[] shadowPoly = new float[coordinates.length * 3]; for (int i = 0; i < coordinates.length; i++) { shadowPoly[i * 3 + 0] = coordinates[i][0]; shadowPoly[i * 3 + 1] = coordinates[i][1]; shadowPoly[i * 3 + 2] = coordinates[i][2]; } // TODO: calculate the ambient shadow and mix with Spot shadow. // Calculate the shadow of SpotLight float[] light = SpotShadow.calculateLight(lightSize, LIGHT_POINTS, lightPosX, lightPosY, lightHeight); int stripSize = 3 * SpotShadow.getStripSize(RAY_TRACING_RAYS, RAY_TRACING_LAYERS); if (stripSize < 9) { return; } float[] strip = new float[stripSize]; SpotShadow.calcShadow(light, LIGHT_POINTS, shadowPoly, coordinates.length, RAY_TRACING_RAYS, RAY_TRACING_LAYERS, 1f, strip); ShadowBuffer buff = new ShadowBuffer(canvas.getWidth(), canvas.getHeight()); buff.generateTriangles(strip, SHADOW_STRENGTH); buff.draw(canvas); } }