blob: 8ae212cd2ebfe7752d1a568d533880b33794ed21 [file] [log] [blame]
/*
* 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);
}
}