diff options
Diffstat (limited to 'graphics')
100 files changed, 11544 insertions, 1792 deletions
diff --git a/graphics/TEST_MAPPING b/graphics/TEST_MAPPING new file mode 100644 index 000000000000..10bd0ee906fd --- /dev/null +++ b/graphics/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "CtsGraphicsTestCases" + } + ] +} diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java index 71ee6c2b4421..6e7f286d19a7 100644 --- a/graphics/java/android/graphics/BaseCanvas.java +++ b/graphics/java/android/graphics/BaseCanvas.java @@ -17,20 +17,23 @@ package android.graphics; import android.annotation.ColorInt; +import android.annotation.ColorLong; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Size; +import android.annotation.UnsupportedAppUsage; import android.graphics.Canvas.VertexMode; +import android.graphics.text.MeasuredText; import android.text.GraphicsOperations; +import android.text.MeasuredParagraph; import android.text.PrecomputedText; import android.text.SpannableString; import android.text.SpannedString; import android.text.TextUtils; -import android.view.RecordingCanvas; /** * This class is a base class for Canvas's drawing operations. Any modifications here - * should be accompanied by a similar modification to {@link RecordingCanvas}. + * should be accompanied by a similar modification to {@link BaseRecordingCanvas}. * * The purpose of this class is to minimize the cost of deciding between regular JNI * and @FastNative JNI to just the virtual call that Canvas already has. @@ -43,6 +46,7 @@ public abstract class BaseCanvas { * freed by NativeAllocation. * @hide */ + @UnsupportedAppUsage protected long mNativeCanvasWrapper; /** @@ -81,7 +85,7 @@ public abstract class BaseCanvas { // --------------------------------------------------------------------------- // Drawing methods - // These are also implemented in DisplayListCanvas so that we can + // These are also implemented in RecordingCanvas so that we can // selectively apply on them // Everything below here is copy/pasted from Canvas.java // The JNI registration is handled by android_view_Canvas.cpp @@ -108,14 +112,14 @@ public abstract class BaseCanvas { public void drawBitmap(@NonNull Bitmap bitmap, float left, float top, @Nullable Paint paint) { throwIfCannotDraw(bitmap); throwIfHasHwBitmapInSwMode(paint); - nDrawBitmap(mNativeCanvasWrapper, bitmap, left, top, + nDrawBitmap(mNativeCanvasWrapper, bitmap.getNativeInstance(), left, top, paint != null ? paint.getNativeInstance() : 0, mDensity, mScreenDensity, bitmap.mDensity); } public void drawBitmap(@NonNull Bitmap bitmap, @NonNull Matrix matrix, @Nullable Paint paint) { throwIfHasHwBitmapInSwMode(paint); - nDrawBitmapMatrix(mNativeCanvasWrapper, bitmap, matrix.ni(), + nDrawBitmapMatrix(mNativeCanvasWrapper, bitmap.getNativeInstance(), matrix.ni(), paint != null ? paint.getNativeInstance() : 0); } @@ -140,7 +144,7 @@ public abstract class BaseCanvas { bottom = src.bottom; } - nDrawBitmap(mNativeCanvasWrapper, bitmap, left, top, right, bottom, + nDrawBitmap(mNativeCanvasWrapper, bitmap.getNativeInstance(), left, top, right, bottom, dst.left, dst.top, dst.right, dst.bottom, nativePaint, mScreenDensity, bitmap.mDensity); } @@ -166,7 +170,7 @@ public abstract class BaseCanvas { bottom = src.bottom; } - nDrawBitmap(mNativeCanvasWrapper, bitmap, left, top, right, bottom, + nDrawBitmap(mNativeCanvasWrapper, bitmap.getNativeInstance(), left, top, right, bottom, dst.left, dst.top, dst.right, dst.bottom, nativePaint, mScreenDensity, bitmap.mDensity); } @@ -225,7 +229,7 @@ public abstract class BaseCanvas { // no mul by 2, since we need only 1 color per vertex checkRange(colors.length, colorOffset, count); } - nDrawBitmapMesh(mNativeCanvasWrapper, bitmap, meshWidth, meshHeight, + nDrawBitmapMesh(mNativeCanvasWrapper, bitmap.getNativeInstance(), meshWidth, meshHeight, verts, vertOffset, colors, colorOffset, paint != null ? paint.getNativeInstance() : 0); } @@ -236,13 +240,31 @@ public abstract class BaseCanvas { } public void drawColor(@ColorInt int color) { - nDrawColor(mNativeCanvasWrapper, color, PorterDuff.Mode.SRC_OVER.nativeInt); + nDrawColor(mNativeCanvasWrapper, color, BlendMode.SRC_OVER.getXfermode().porterDuffMode); } public void drawColor(@ColorInt int color, @NonNull PorterDuff.Mode mode) { nDrawColor(mNativeCanvasWrapper, color, mode.nativeInt); } + /** + * Make lint happy. + * See {@link Canvas#drawColor(int, BlendMode)} + */ + public void drawColor(@ColorInt int color, @NonNull BlendMode mode) { + nDrawColor(mNativeCanvasWrapper, color, mode.getXfermode().porterDuffMode); + } + + /** + * Make lint happy. + * See {@link Canvas#drawColor(long, BlendMode)} + */ + public void drawColor(@ColorLong long color, @NonNull BlendMode mode) { + ColorSpace cs = Color.colorSpace(color); + nDrawColor(mNativeCanvasWrapper, cs.getNativeInstance(), color, + mode.getXfermode().porterDuffMode); + } + public void drawLine(float startX, float startY, float stopX, float stopY, @NonNull Paint paint) { throwIfHasHwBitmapInSwMode(paint); @@ -374,6 +396,53 @@ public abstract class BaseCanvas { drawRoundRect(rect.left, rect.top, rect.right, rect.bottom, rx, ry, paint); } + /** + * Make lint happy. + * See {@link Canvas#drawDoubleRoundRect(RectF, float, float, RectF, float, float, Paint)} + */ + public void drawDoubleRoundRect(@NonNull RectF outer, float outerRx, float outerRy, + @NonNull RectF inner, float innerRx, float innerRy, @NonNull Paint paint) { + throwIfHasHwBitmapInSwMode(paint); + float outerLeft = outer.left; + float outerTop = outer.top; + float outerRight = outer.right; + float outerBottom = outer.bottom; + + float innerLeft = inner.left; + float innerTop = inner.top; + float innerRight = inner.right; + float innerBottom = inner.bottom; + nDrawDoubleRoundRect(mNativeCanvasWrapper, outerLeft, outerTop, outerRight, outerBottom, + outerRx, outerRy, innerLeft, innerTop, innerRight, innerBottom, innerRx, innerRy, + paint.getNativeInstance()); + } + + /** + * Make lint happy. + * See {@link Canvas#drawDoubleRoundRect(RectF, float[], RectF, float[], Paint)} + */ + public void drawDoubleRoundRect(@NonNull RectF outer, @NonNull float[] outerRadii, + @NonNull RectF inner, @NonNull float[] innerRadii, @NonNull Paint paint) { + throwIfHasHwBitmapInSwMode(paint); + if (innerRadii == null || outerRadii == null + || innerRadii.length != 8 || outerRadii.length != 8) { + throw new IllegalArgumentException("Both inner and outer radii arrays must contain " + + "exactly 8 values"); + } + float outerLeft = outer.left; + float outerTop = outer.top; + float outerRight = outer.right; + float outerBottom = outer.bottom; + + float innerLeft = inner.left; + float innerTop = inner.top; + float innerRight = inner.right; + float innerBottom = inner.bottom; + nDrawDoubleRoundRect(mNativeCanvasWrapper, outerLeft, outerTop, outerRight, + outerBottom, outerRadii, innerLeft, innerTop, innerRight, innerBottom, innerRadii, + paint.getNativeInstance()); + } + public void drawText(@NonNull char[] text, int index, int count, float x, float y, @NonNull Paint paint) { if ((index | count | (index + count) | @@ -486,33 +555,46 @@ public abstract class BaseCanvas { ((GraphicsOperations) text).drawTextRun(this, start, end, contextStart, contextEnd, x, y, isRtl, paint); } else { + if (text instanceof PrecomputedText) { + final PrecomputedText pt = (PrecomputedText) text; + final int paraIndex = pt.findParaIndex(start); + if (end <= pt.getParagraphEnd(paraIndex)) { + final int paraStart = pt.getParagraphStart(paraIndex); + final MeasuredParagraph mp = pt.getMeasuredParagraph(paraIndex); + // Only support the text in the same paragraph. + drawTextRun(mp.getMeasuredText(), + start - paraStart, + end - paraStart, + contextStart - paraStart, + contextEnd - paraStart, + x, y, isRtl, paint); + return; + } + } int contextLen = contextEnd - contextStart; int len = end - start; char[] buf = TemporaryBuffer.obtain(contextLen); TextUtils.getChars(text, contextStart, contextEnd, buf, 0); - long measuredTextPtr = 0; - if (text instanceof PrecomputedText) { - PrecomputedText mt = (PrecomputedText) text; - int paraIndex = mt.findParaIndex(start); - if (end <= mt.getParagraphEnd(paraIndex)) { - // Only suppor the text in the same paragraph. - measuredTextPtr = mt.getMeasuredParagraph(paraIndex).getNativePtr(); - } - } nDrawTextRun(mNativeCanvasWrapper, buf, start - contextStart, len, - 0, contextLen, x, y, isRtl, paint.getNativeInstance(), measuredTextPtr); + 0, contextLen, x, y, isRtl, paint.getNativeInstance(), + 0 /* measured paragraph pointer */); TemporaryBuffer.recycle(buf); } } + public void drawTextRun(@NonNull MeasuredText measuredText, int start, int end, + int contextStart, int contextEnd, float x, float y, boolean isRtl, + @NonNull Paint paint) { + nDrawTextRun(mNativeCanvasWrapper, measuredText.getChars(), start, end - start, + contextStart, contextEnd - contextStart, x, y, isRtl, paint.getNativeInstance(), + measuredText.getNativePtr()); + } + public void drawVertices(@NonNull VertexMode mode, int vertexCount, @NonNull float[] verts, int vertOffset, @Nullable float[] texs, int texOffset, @Nullable int[] colors, int colorOffset, @Nullable short[] indices, int indexOffset, int indexCount, @NonNull Paint paint) { checkRange(verts.length, vertOffset, vertexCount); - if (isHardwareAccelerated()) { - return; - } if (texs != null) { checkRange(texs.length, texOffset, vertexCount); } @@ -578,10 +660,11 @@ public abstract class BaseCanvas { } } - private static native void nDrawBitmap(long nativeCanvas, Bitmap bitmap, float left, float top, - long nativePaintOrZero, int canvasDensity, int screenDensity, int bitmapDensity); + private static native void nDrawBitmap(long nativeCanvas, long bitmapHandle, float left, + float top, long nativePaintOrZero, int canvasDensity, int screenDensity, + int bitmapDensity); - private static native void nDrawBitmap(long nativeCanvas, Bitmap bitmap, float srcLeft, + private static native void nDrawBitmap(long nativeCanvas, long bitmapHandle, float srcLeft, float srcTop, float srcRight, float srcBottom, float dstLeft, float dstTop, float dstRight, float dstBottom, long nativePaintOrZero, int screenDensity, int bitmapDensity); @@ -591,6 +674,9 @@ public abstract class BaseCanvas { private static native void nDrawColor(long nativeCanvas, int color, int mode); + private static native void nDrawColor(long nativeCanvas, long nativeColorSpace, + @ColorLong long color, int mode); + private static native void nDrawPaint(long nativeCanvas, long nativePaint); private static native void nDrawPoint(long canvasHandle, float x, float y, long paintHandle); @@ -619,6 +705,16 @@ public abstract class BaseCanvas { private static native void nDrawRoundRect(long nativeCanvas, float left, float top, float right, float bottom, float rx, float ry, long nativePaint); + private static native void nDrawDoubleRoundRect(long nativeCanvas, float outerLeft, + float outerTop, float outerRight, float outerBottom, float outerRx, float outerRy, + float innerLeft, float innerTop, float innerRight, float innerBottom, float innerRx, + float innerRy, long nativePaint); + + private static native void nDrawDoubleRoundRect(long nativeCanvas, float outerLeft, + float outerTop, float outerRight, float outerBottom, float[] outerRadii, + float innerLeft, float innerTop, float innerRight, float innerBottom, + float[] innerRadii, long nativePaint); + private static native void nDrawPath(long nativeCanvas, long nativePath, long nativePaint); private static native void nDrawRegion(long nativeCanvas, long nativeRegion, long nativePaint); @@ -627,10 +723,10 @@ public abstract class BaseCanvas { float dstLeft, float dstTop, float dstRight, float dstBottom, long nativePaintOrZero, int screenDensity, int bitmapDensity); - private static native void nDrawBitmapMatrix(long nativeCanvas, Bitmap bitmap, + private static native void nDrawBitmapMatrix(long nativeCanvas, long bitmapHandle, long nativeMatrix, long nativePaint); - private static native void nDrawBitmapMesh(long nativeCanvas, Bitmap bitmap, int meshWidth, + private static native void nDrawBitmapMesh(long nativeCanvas, long bitmapHandle, int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, int colorOffset, long nativePaint); diff --git a/graphics/java/android/graphics/BaseRecordingCanvas.java b/graphics/java/android/graphics/BaseRecordingCanvas.java new file mode 100644 index 000000000000..2f5214cb1df5 --- /dev/null +++ b/graphics/java/android/graphics/BaseRecordingCanvas.java @@ -0,0 +1,700 @@ +/* + * Copyright (C) 2016 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.graphics; + +import android.annotation.ColorInt; +import android.annotation.ColorLong; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.Size; +import android.graphics.text.MeasuredText; +import android.text.GraphicsOperations; +import android.text.MeasuredParagraph; +import android.text.PrecomputedText; +import android.text.SpannableString; +import android.text.SpannedString; +import android.text.TextUtils; + +import dalvik.annotation.optimization.FastNative; + +/** + * This class is a base class for canvases that defer drawing operations, so all + * the draw operations can be marked @FastNative. It contains a re-implementation of + * all the methods in {@link BaseCanvas}. + * + * @hide + */ +public class BaseRecordingCanvas extends Canvas { + + public BaseRecordingCanvas(long nativeCanvas) { + super(nativeCanvas); + } + + @Override + public final void drawArc(float left, float top, float right, float bottom, float startAngle, + float sweepAngle, boolean useCenter, @NonNull Paint paint) { + nDrawArc(mNativeCanvasWrapper, left, top, right, bottom, startAngle, sweepAngle, + useCenter, paint.getNativeInstance()); + } + + @Override + public final void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, + boolean useCenter, @NonNull Paint paint) { + drawArc(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle, useCenter, + paint); + } + + @Override + public final void drawARGB(int a, int r, int g, int b) { + drawColor(Color.argb(a, r, g, b)); + } + + @Override + public final void drawBitmap(@NonNull Bitmap bitmap, float left, float top, + @Nullable Paint paint) { + throwIfCannotDraw(bitmap); + nDrawBitmap(mNativeCanvasWrapper, bitmap.getNativeInstance(), left, top, + paint != null ? paint.getNativeInstance() : 0, mDensity, mScreenDensity, + bitmap.mDensity); + } + + @Override + public final void drawBitmap(@NonNull Bitmap bitmap, @NonNull Matrix matrix, + @Nullable Paint paint) { + nDrawBitmapMatrix(mNativeCanvasWrapper, bitmap.getNativeInstance(), matrix.ni(), + paint != null ? paint.getNativeInstance() : 0); + } + + @Override + public final void drawBitmap(@NonNull Bitmap bitmap, @Nullable Rect src, @NonNull Rect dst, + @Nullable Paint paint) { + if (dst == null) { + throw new NullPointerException(); + } + throwIfCannotDraw(bitmap); + final long nativePaint = paint == null ? 0 : paint.getNativeInstance(); + + int left, top, right, bottom; + if (src == null) { + left = top = 0; + right = bitmap.getWidth(); + bottom = bitmap.getHeight(); + } else { + left = src.left; + right = src.right; + top = src.top; + bottom = src.bottom; + } + + nDrawBitmap(mNativeCanvasWrapper, bitmap.getNativeInstance(), left, top, right, bottom, + dst.left, dst.top, dst.right, dst.bottom, nativePaint, mScreenDensity, + bitmap.mDensity); + } + + @Override + public final void drawBitmap(@NonNull Bitmap bitmap, @Nullable Rect src, @NonNull RectF dst, + @Nullable Paint paint) { + if (dst == null) { + throw new NullPointerException(); + } + throwIfCannotDraw(bitmap); + final long nativePaint = paint == null ? 0 : paint.getNativeInstance(); + + float left, top, right, bottom; + if (src == null) { + left = top = 0; + right = bitmap.getWidth(); + bottom = bitmap.getHeight(); + } else { + left = src.left; + right = src.right; + top = src.top; + bottom = src.bottom; + } + + nDrawBitmap(mNativeCanvasWrapper, bitmap.getNativeInstance(), left, top, right, bottom, + dst.left, dst.top, dst.right, dst.bottom, nativePaint, mScreenDensity, + bitmap.mDensity); + } + + /** @deprecated checkstyle */ + @Override + @Deprecated + public final void drawBitmap(@NonNull int[] colors, int offset, int stride, float x, float y, + int width, int height, boolean hasAlpha, @Nullable Paint paint) { + // check for valid input + if (width < 0) { + throw new IllegalArgumentException("width must be >= 0"); + } + if (height < 0) { + throw new IllegalArgumentException("height must be >= 0"); + } + if (Math.abs(stride) < width) { + throw new IllegalArgumentException("abs(stride) must be >= width"); + } + int lastScanline = offset + (height - 1) * stride; + int length = colors.length; + if (offset < 0 || (offset + width > length) || lastScanline < 0 + || (lastScanline + width > length)) { + throw new ArrayIndexOutOfBoundsException(); + } + // quick escape if there's nothing to draw + if (width == 0 || height == 0) { + return; + } + // punch down to native for the actual draw + nDrawBitmap(mNativeCanvasWrapper, colors, offset, stride, x, y, width, height, hasAlpha, + paint != null ? paint.getNativeInstance() : 0); + } + + /** @deprecated checkstyle */ + @Override + @Deprecated + public final void drawBitmap(@NonNull int[] colors, int offset, int stride, int x, int y, + int width, int height, boolean hasAlpha, @Nullable Paint paint) { + // call through to the common float version + drawBitmap(colors, offset, stride, (float) x, (float) y, width, height, + hasAlpha, paint); + } + + @Override + public final void drawBitmapMesh(@NonNull Bitmap bitmap, int meshWidth, int meshHeight, + @NonNull float[] verts, int vertOffset, @Nullable int[] colors, int colorOffset, + @Nullable Paint paint) { + if ((meshWidth | meshHeight | vertOffset | colorOffset) < 0) { + throw new ArrayIndexOutOfBoundsException(); + } + if (meshWidth == 0 || meshHeight == 0) { + return; + } + int count = (meshWidth + 1) * (meshHeight + 1); + // we mul by 2 since we need two floats per vertex + checkRange(verts.length, vertOffset, count * 2); + if (colors != null) { + // no mul by 2, since we need only 1 color per vertex + checkRange(colors.length, colorOffset, count); + } + nDrawBitmapMesh(mNativeCanvasWrapper, bitmap.getNativeInstance(), meshWidth, meshHeight, + verts, vertOffset, colors, colorOffset, + paint != null ? paint.getNativeInstance() : 0); + } + + @Override + public final void drawCircle(float cx, float cy, float radius, @NonNull Paint paint) { + nDrawCircle(mNativeCanvasWrapper, cx, cy, radius, paint.getNativeInstance()); + } + + @Override + public final void drawColor(@ColorInt int color) { + nDrawColor(mNativeCanvasWrapper, color, BlendMode.SRC_OVER.getXfermode().porterDuffMode); + } + + @Override + public final void drawColor(@ColorInt int color, @NonNull PorterDuff.Mode mode) { + nDrawColor(mNativeCanvasWrapper, color, mode.nativeInt); + } + + @Override + public final void drawColor(@ColorInt int color, @NonNull BlendMode mode) { + nDrawColor(mNativeCanvasWrapper, color, mode.getXfermode().porterDuffMode); + } + + @Override + public final void drawColor(@ColorLong long color, @NonNull BlendMode mode) { + ColorSpace cs = Color.colorSpace(color); + nDrawColor(mNativeCanvasWrapper, cs.getNativeInstance(), color, + mode.getXfermode().porterDuffMode); + } + + @Override + public final void drawLine(float startX, float startY, float stopX, float stopY, + @NonNull Paint paint) { + nDrawLine(mNativeCanvasWrapper, startX, startY, stopX, stopY, paint.getNativeInstance()); + } + + @Override + public final void drawLines(@Size(multiple = 4) @NonNull float[] pts, int offset, int count, + @NonNull Paint paint) { + nDrawLines(mNativeCanvasWrapper, pts, offset, count, paint.getNativeInstance()); + } + + @Override + public final void drawLines(@Size(multiple = 4) @NonNull float[] pts, @NonNull Paint paint) { + drawLines(pts, 0, pts.length, paint); + } + + @Override + public final void drawOval(float left, float top, float right, float bottom, + @NonNull Paint paint) { + nDrawOval(mNativeCanvasWrapper, left, top, right, bottom, paint.getNativeInstance()); + } + + @Override + public final void drawOval(@NonNull RectF oval, @NonNull Paint paint) { + if (oval == null) { + throw new NullPointerException(); + } + drawOval(oval.left, oval.top, oval.right, oval.bottom, paint); + } + + @Override + public final void drawPaint(@NonNull Paint paint) { + nDrawPaint(mNativeCanvasWrapper, paint.getNativeInstance()); + } + + @Override + public final void drawPatch(@NonNull NinePatch patch, @NonNull Rect dst, + @Nullable Paint paint) { + Bitmap bitmap = patch.getBitmap(); + throwIfCannotDraw(bitmap); + final long nativePaint = paint == null ? 0 : paint.getNativeInstance(); + nDrawNinePatch(mNativeCanvasWrapper, bitmap.getNativeInstance(), patch.mNativeChunk, + dst.left, dst.top, dst.right, dst.bottom, nativePaint, + mDensity, patch.getDensity()); + } + + @Override + public final void drawPatch(@NonNull NinePatch patch, @NonNull RectF dst, + @Nullable Paint paint) { + Bitmap bitmap = patch.getBitmap(); + throwIfCannotDraw(bitmap); + final long nativePaint = paint == null ? 0 : paint.getNativeInstance(); + nDrawNinePatch(mNativeCanvasWrapper, bitmap.getNativeInstance(), patch.mNativeChunk, + dst.left, dst.top, dst.right, dst.bottom, nativePaint, + mDensity, patch.getDensity()); + } + + @Override + public final void drawPath(@NonNull Path path, @NonNull Paint paint) { + if (path.isSimplePath && path.rects != null) { + nDrawRegion(mNativeCanvasWrapper, path.rects.mNativeRegion, paint.getNativeInstance()); + } else { + nDrawPath(mNativeCanvasWrapper, path.readOnlyNI(), paint.getNativeInstance()); + } + } + + @Override + public final void drawPicture(@NonNull Picture picture) { + picture.endRecording(); + int restoreCount = save(); + picture.draw(this); + restoreToCount(restoreCount); + } + + @Override + public final void drawPicture(@NonNull Picture picture, @NonNull Rect dst) { + save(); + translate(dst.left, dst.top); + if (picture.getWidth() > 0 && picture.getHeight() > 0) { + scale((float) dst.width() / picture.getWidth(), + (float) dst.height() / picture.getHeight()); + } + drawPicture(picture); + restore(); + } + + @Override + public final void drawPicture(@NonNull Picture picture, @NonNull RectF dst) { + save(); + translate(dst.left, dst.top); + if (picture.getWidth() > 0 && picture.getHeight() > 0) { + scale(dst.width() / picture.getWidth(), dst.height() / picture.getHeight()); + } + drawPicture(picture); + restore(); + } + + @Override + public final void drawPoint(float x, float y, @NonNull Paint paint) { + nDrawPoint(mNativeCanvasWrapper, x, y, paint.getNativeInstance()); + } + + @Override + public final void drawPoints(@Size(multiple = 2) float[] pts, int offset, int count, + @NonNull Paint paint) { + nDrawPoints(mNativeCanvasWrapper, pts, offset, count, paint.getNativeInstance()); + } + + @Override + public final void drawPoints(@Size(multiple = 2) @NonNull float[] pts, @NonNull Paint paint) { + drawPoints(pts, 0, pts.length, paint); + } + + /** @deprecated checkstyle */ + @Override + @Deprecated + public final void drawPosText(@NonNull char[] text, int index, int count, + @NonNull @Size(multiple = 2) float[] pos, + @NonNull Paint paint) { + if (index < 0 || index + count > text.length || count * 2 > pos.length) { + throw new IndexOutOfBoundsException(); + } + for (int i = 0; i < count; i++) { + drawText(text, index + i, 1, pos[i * 2], pos[i * 2 + 1], paint); + } + } + + /** @deprecated checkstyle */ + @Override + @Deprecated + public final void drawPosText(@NonNull String text, @NonNull @Size(multiple = 2) float[] pos, + @NonNull Paint paint) { + drawPosText(text.toCharArray(), 0, text.length(), pos, paint); + } + + @Override + public final void drawRect(float left, float top, float right, float bottom, + @NonNull Paint paint) { + nDrawRect(mNativeCanvasWrapper, left, top, right, bottom, paint.getNativeInstance()); + } + + @Override + public final void drawRect(@NonNull Rect r, @NonNull Paint paint) { + drawRect(r.left, r.top, r.right, r.bottom, paint); + } + + @Override + public final void drawRect(@NonNull RectF rect, @NonNull Paint paint) { + nDrawRect(mNativeCanvasWrapper, + rect.left, rect.top, rect.right, rect.bottom, paint.getNativeInstance()); + } + + @Override + public final void drawRGB(int r, int g, int b) { + drawColor(Color.rgb(r, g, b)); + } + + @Override + public final void drawRoundRect(float left, float top, float right, float bottom, + float rx, float ry, @NonNull Paint paint) { + nDrawRoundRect(mNativeCanvasWrapper, left, top, right, bottom, rx, ry, + paint.getNativeInstance()); + } + + @Override + public final void drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint) { + drawRoundRect(rect.left, rect.top, rect.right, rect.bottom, rx, ry, paint); + } + + @Override + public final void drawDoubleRoundRect(@NonNull RectF outer, float outerRx, float outerRy, + @NonNull RectF inner, float innerRx, float innerRy, @NonNull Paint paint) { + nDrawDoubleRoundRect(mNativeCanvasWrapper, + outer.left, outer.top, outer.right, outer.bottom, outerRx, outerRy, + inner.left, inner.top, inner.right, inner.bottom, innerRx, innerRy, + paint.getNativeInstance()); + } + + @Override + public final void drawDoubleRoundRect(@NonNull RectF outer, float[] outerRadii, + @NonNull RectF inner, float[] innerRadii, @NonNull Paint paint) { + nDrawDoubleRoundRect(mNativeCanvasWrapper, + outer.left, outer.top, outer.right, outer.bottom, outerRadii, + inner.left, inner.top, inner.right, inner.bottom, innerRadii, + paint.getNativeInstance()); + } + + @Override + public final void drawText(@NonNull char[] text, int index, int count, float x, float y, + @NonNull Paint paint) { + if ((index | count | (index + count) + | (text.length - index - count)) < 0) { + throw new IndexOutOfBoundsException(); + } + nDrawText(mNativeCanvasWrapper, text, index, count, x, y, paint.mBidiFlags, + paint.getNativeInstance()); + } + + @Override + public final void drawText(@NonNull CharSequence text, int start, int end, float x, float y, + @NonNull Paint paint) { + if ((start | end | (end - start) | (text.length() - end)) < 0) { + throw new IndexOutOfBoundsException(); + } + if (text instanceof String || text instanceof SpannedString + || text instanceof SpannableString) { + nDrawText(mNativeCanvasWrapper, text.toString(), start, end, x, y, + paint.mBidiFlags, paint.getNativeInstance()); + } else if (text instanceof GraphicsOperations) { + ((GraphicsOperations) text).drawText(this, start, end, x, y, + paint); + } else { + char[] buf = TemporaryBuffer.obtain(end - start); + TextUtils.getChars(text, start, end, buf, 0); + nDrawText(mNativeCanvasWrapper, buf, 0, end - start, x, y, + paint.mBidiFlags, paint.getNativeInstance()); + TemporaryBuffer.recycle(buf); + } + } + + @Override + public final void drawText(@NonNull String text, float x, float y, @NonNull Paint paint) { + nDrawText(mNativeCanvasWrapper, text, 0, text.length(), x, y, paint.mBidiFlags, + paint.getNativeInstance()); + } + + @Override + public final void drawText(@NonNull String text, int start, int end, float x, float y, + @NonNull Paint paint) { + if ((start | end | (end - start) | (text.length() - end)) < 0) { + throw new IndexOutOfBoundsException(); + } + nDrawText(mNativeCanvasWrapper, text, start, end, x, y, paint.mBidiFlags, + paint.getNativeInstance()); + } + + @Override + public final void drawTextOnPath(@NonNull char[] text, int index, int count, @NonNull Path path, + float hOffset, float vOffset, @NonNull Paint paint) { + if (index < 0 || index + count > text.length) { + throw new ArrayIndexOutOfBoundsException(); + } + nDrawTextOnPath(mNativeCanvasWrapper, text, index, count, + path.readOnlyNI(), hOffset, vOffset, + paint.mBidiFlags, paint.getNativeInstance()); + } + + @Override + public final void drawTextOnPath(@NonNull String text, @NonNull Path path, float hOffset, + float vOffset, @NonNull Paint paint) { + if (text.length() > 0) { + nDrawTextOnPath(mNativeCanvasWrapper, text, path.readOnlyNI(), hOffset, vOffset, + paint.mBidiFlags, paint.getNativeInstance()); + } + } + + @Override + public final void drawTextRun(@NonNull char[] text, int index, int count, int contextIndex, + int contextCount, float x, float y, boolean isRtl, @NonNull Paint paint) { + + if (text == null) { + throw new NullPointerException("text is null"); + } + if (paint == null) { + throw new NullPointerException("paint is null"); + } + if ((index | count | contextIndex | contextCount | index - contextIndex + | (contextIndex + contextCount) - (index + count) + | text.length - (contextIndex + contextCount)) < 0) { + throw new IndexOutOfBoundsException(); + } + + nDrawTextRun(mNativeCanvasWrapper, text, index, count, contextIndex, contextCount, + x, y, isRtl, paint.getNativeInstance(), 0 /* measured text */); + } + + @Override + public final void drawTextRun(@NonNull CharSequence text, int start, int end, int contextStart, + int contextEnd, float x, float y, boolean isRtl, @NonNull Paint paint) { + + if (text == null) { + throw new NullPointerException("text is null"); + } + if (paint == null) { + throw new NullPointerException("paint is null"); + } + if ((start | end | contextStart | contextEnd | start - contextStart | end - start + | contextEnd - end | text.length() - contextEnd) < 0) { + throw new IndexOutOfBoundsException(); + } + + if (text instanceof String || text instanceof SpannedString + || text instanceof SpannableString) { + nDrawTextRun(mNativeCanvasWrapper, text.toString(), start, end, contextStart, + contextEnd, x, y, isRtl, paint.getNativeInstance()); + } else if (text instanceof GraphicsOperations) { + ((GraphicsOperations) text).drawTextRun(this, start, end, + contextStart, contextEnd, x, y, isRtl, paint); + } else { + if (text instanceof PrecomputedText) { + final PrecomputedText pt = (PrecomputedText) text; + final int paraIndex = pt.findParaIndex(start); + if (end <= pt.getParagraphEnd(paraIndex)) { + final int paraStart = pt.getParagraphStart(paraIndex); + final MeasuredParagraph mp = pt.getMeasuredParagraph(paraIndex); + // Only support if the target is in the same paragraph. + drawTextRun(mp.getMeasuredText(), + start - paraStart, + end - paraStart, + contextStart - paraStart, + contextEnd - paraStart, + x, y, isRtl, paint); + return; + } + } + int contextLen = contextEnd - contextStart; + int len = end - start; + char[] buf = TemporaryBuffer.obtain(contextLen); + TextUtils.getChars(text, contextStart, contextEnd, buf, 0); + nDrawTextRun(mNativeCanvasWrapper, buf, start - contextStart, len, + 0, contextLen, x, y, isRtl, paint.getNativeInstance(), + 0 /* measured paragraph pointer */); + TemporaryBuffer.recycle(buf); + } + } + + @Override + public void drawTextRun(@NonNull MeasuredText measuredText, int start, int end, + int contextStart, int contextEnd, float x, float y, boolean isRtl, + @NonNull Paint paint) { + nDrawTextRun(mNativeCanvasWrapper, measuredText.getChars(), start, end - start, + contextStart, contextEnd - contextStart, x, y, isRtl, paint.getNativeInstance(), + measuredText.getNativePtr()); + } + + @Override + public final void drawVertices(@NonNull VertexMode mode, int vertexCount, + @NonNull float[] verts, int vertOffset, @Nullable float[] texs, int texOffset, + @Nullable int[] colors, int colorOffset, @Nullable short[] indices, int indexOffset, + int indexCount, @NonNull Paint paint) { + checkRange(verts.length, vertOffset, vertexCount); + if (texs != null) { + checkRange(texs.length, texOffset, vertexCount); + } + if (colors != null) { + checkRange(colors.length, colorOffset, vertexCount / 2); + } + if (indices != null) { + checkRange(indices.length, indexOffset, indexCount); + } + nDrawVertices(mNativeCanvasWrapper, mode.nativeInt, vertexCount, verts, + vertOffset, texs, texOffset, colors, colorOffset, + indices, indexOffset, indexCount, paint.getNativeInstance()); + } + + @FastNative + private static native void nDrawBitmap(long nativeCanvas, long bitmapHandle, float left, + float top, long nativePaintOrZero, int canvasDensity, int screenDensity, + int bitmapDensity); + + @FastNative + private static native void nDrawBitmap(long nativeCanvas, long bitmapHandle, + float srcLeft, float srcTop, float srcRight, float srcBottom, + float dstLeft, float dstTop, float dstRight, float dstBottom, + long nativePaintOrZero, int screenDensity, int bitmapDensity); + + @FastNative + private static native void nDrawBitmap(long nativeCanvas, int[] colors, int offset, int stride, + float x, float y, int width, int height, boolean hasAlpha, long nativePaintOrZero); + + @FastNative + private static native void nDrawColor(long nativeCanvas, int color, int mode); + + @FastNative + private static native void nDrawColor(long nativeCanvas, long nativeColorSpace, + @ColorLong long color, int mode); + + @FastNative + private static native void nDrawPaint(long nativeCanvas, long nativePaint); + + @FastNative + private static native void nDrawPoint(long canvasHandle, float x, float y, long paintHandle); + + @FastNative + private static native void nDrawPoints(long canvasHandle, float[] pts, int offset, int count, + long paintHandle); + + @FastNative + private static native void nDrawLine(long nativeCanvas, float startX, float startY, float stopX, + float stopY, long nativePaint); + + @FastNative + private static native void nDrawLines(long canvasHandle, float[] pts, int offset, int count, + long paintHandle); + + @FastNative + private static native void nDrawRect(long nativeCanvas, float left, float top, float right, + float bottom, long nativePaint); + + @FastNative + private static native void nDrawOval(long nativeCanvas, float left, float top, float right, + float bottom, long nativePaint); + + @FastNative + private static native void nDrawCircle(long nativeCanvas, float cx, float cy, float radius, + long nativePaint); + + @FastNative + private static native void nDrawArc(long nativeCanvas, float left, float top, float right, + float bottom, float startAngle, float sweep, boolean useCenter, long nativePaint); + + @FastNative + private static native void nDrawRoundRect(long nativeCanvas, float left, float top, float right, + float bottom, float rx, float ry, long nativePaint); + + @FastNative + private static native void nDrawDoubleRoundRect(long nativeCanvas, + float outerLeft, float outerTop, float outerRight, float outerBottom, + float outerRx, float outerRy, float innerLeft, float innerTop, float innerRight, + float innerBottom, float innerRx, float innerRy, long nativePaint); + + @FastNative + private static native void nDrawDoubleRoundRect(long nativeCanvas, float outerLeft, + float outerTop, float outerRight, float outerBottom, float[] outerRadii, + float innerLeft, float innerTop, float innerRight, float innerBottom, + float[] innerRadii, long nativePaint); + + @FastNative + private static native void nDrawPath(long nativeCanvas, long nativePath, long nativePaint); + + @FastNative + private static native void nDrawRegion(long nativeCanvas, long nativeRegion, long nativePaint); + + @FastNative + private static native void nDrawNinePatch(long nativeCanvas, long nativeBitmap, long ninePatch, + float dstLeft, float dstTop, float dstRight, float dstBottom, long nativePaintOrZero, + int screenDensity, int bitmapDensity); + + @FastNative + private static native void nDrawBitmapMatrix(long nativeCanvas, long bitmapHandle, + long nativeMatrix, long nativePaint); + + @FastNative + private static native void nDrawBitmapMesh(long nativeCanvas, long bitmapHandle, int meshWidth, + int meshHeight, float[] verts, int vertOffset, int[] colors, int colorOffset, + long nativePaint); + + @FastNative + private static native void nDrawVertices(long nativeCanvas, int mode, int n, float[] verts, + int vertOffset, float[] texs, int texOffset, int[] colors, int colorOffset, + short[] indices, int indexOffset, int indexCount, long nativePaint); + + @FastNative + private static native void nDrawText(long nativeCanvas, char[] text, int index, int count, + float x, float y, int flags, long nativePaint); + + @FastNative + private static native void nDrawText(long nativeCanvas, String text, int start, int end, + float x, float y, int flags, long nativePaint); + + @FastNative + private static native void nDrawTextRun(long nativeCanvas, String text, int start, int end, + int contextStart, int contextEnd, float x, float y, boolean isRtl, long nativePaint); + + @FastNative + private static native void nDrawTextRun(long nativeCanvas, char[] text, int start, int count, + int contextStart, int contextCount, float x, float y, boolean isRtl, long nativePaint, + long nativePrecomputedText); + + @FastNative + private static native void nDrawTextOnPath(long nativeCanvas, char[] text, int index, int count, + long nativePath, float hOffset, float vOffset, int bidiFlags, long nativePaint); + + @FastNative + private static native void nDrawTextOnPath(long nativeCanvas, String text, long nativePath, + float hOffset, float vOffset, int flags, long nativePaint); +} diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index 95a0c56905c0..44710178da5e 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -18,21 +18,25 @@ package android.graphics; import android.annotation.CheckResult; import android.annotation.ColorInt; +import android.annotation.ColorLong; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.Size; +import android.annotation.UnsupportedAppUsage; import android.annotation.WorkerThread; import android.content.res.ResourcesImpl; +import android.hardware.HardwareBuffer; +import android.os.Build; import android.os.Parcel; import android.os.Parcelable; import android.os.StrictMode; import android.os.Trace; import android.util.DisplayMetrics; +import android.util.Half; import android.util.Log; -import android.view.DisplayListCanvas; -import android.view.RenderNode; import android.view.ThreadedRenderer; +import dalvik.annotation.optimization.CriticalNative; + import libcore.util.NativeAllocationRegistry; import java.io.OutputStream; @@ -57,10 +61,9 @@ public final class Bitmap implements Parcelable { private static final long NATIVE_ALLOCATION_SIZE = 32; // Convenience for JNI access + @UnsupportedAppUsage private final long mNativePtr; - private final boolean mIsMutable; - /** * Represents whether the Bitmap's content is requested to be pre-multiplied. * Note that isPremultiplied() does not directly return this value, because @@ -75,9 +78,13 @@ public final class Bitmap implements Parcelable { */ private boolean mRequestPremultiplied; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769491) private byte[] mNinePatchChunk; // may be null + @UnsupportedAppUsage private NinePatch.InsetStruct mNinePatchInsets; // may be null + @UnsupportedAppUsage private int mWidth; + @UnsupportedAppUsage private int mHeight; private boolean mRecycled; @@ -99,11 +106,13 @@ public final class Bitmap implements Parcelable { * density when running old apps. * @hide */ + @UnsupportedAppUsage public static void setDefaultDensity(int density) { sDefaultDensity = density; } @SuppressWarnings("deprecation") + @UnsupportedAppUsage static int getDefaultDensity() { if (sDefaultDensity >= 0) { return sDefaultDensity; @@ -113,20 +122,28 @@ public final class Bitmap implements Parcelable { } /** - * Private constructor that must received an already allocated native bitmap + * Private constructor that must receive an already allocated native bitmap * int (pointer). */ - // called from JNI + // JNI now calls the version below this one. This is preserved due to UnsupportedAppUsage. + @UnsupportedAppUsage(maxTargetSdk = 28) Bitmap(long nativeBitmap, int width, int height, int density, - boolean isMutable, boolean requestPremultiplied, - byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets) { + boolean requestPremultiplied, byte[] ninePatchChunk, + NinePatch.InsetStruct ninePatchInsets) { + this(nativeBitmap, width, height, density, requestPremultiplied, ninePatchChunk, + ninePatchInsets, true); + } + + // called from JNI and Bitmap_Delegate. + Bitmap(long nativeBitmap, int width, int height, int density, + boolean requestPremultiplied, byte[] ninePatchChunk, + NinePatch.InsetStruct ninePatchInsets, boolean fromMalloc) { if (nativeBitmap == 0) { throw new RuntimeException("internal error: native bitmap is 0"); } mWidth = width; mHeight = height; - mIsMutable = isMutable; mRequestPremultiplied = requestPremultiplied; mNinePatchChunk = ninePatchChunk; @@ -136,13 +153,21 @@ public final class Bitmap implements Parcelable { } mNativePtr = nativeBitmap; - long nativeSize = NATIVE_ALLOCATION_SIZE + getAllocationByteCount(); - NativeAllocationRegistry registry = new NativeAllocationRegistry( - Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), nativeSize); + + final int allocationByteCount = getAllocationByteCount(); + NativeAllocationRegistry registry; + if (fromMalloc) { + registry = NativeAllocationRegistry.createMalloced( + Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), allocationByteCount); + } else { + registry = NativeAllocationRegistry.createNonmalloced( + Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), allocationByteCount); + } registry.registerNativeAllocation(this, nativeBitmap); if (ResourcesImpl.TRACE_FOR_DETAILED_PRELOAD) { sPreloadTracingNumInstantiatedBitmaps++; + long nativeSize = NATIVE_ALLOCATION_SIZE + allocationByteCount; sPreloadTracingTotalBitmapsSize += nativeSize; } } @@ -160,6 +185,7 @@ public final class Bitmap implements Parcelable { * width/height values */ @SuppressWarnings("unused") // called from JNI + @UnsupportedAppUsage void reinit(int width, int height, boolean requestPremultiplied) { mWidth = width; mHeight = height; @@ -330,6 +356,7 @@ public final class Bitmap implements Parcelable { * * @hide */ + @UnsupportedAppUsage public void setNinePatchChunk(byte[] chunk) { mNinePatchChunk = chunk; } @@ -346,14 +373,9 @@ public final class Bitmap implements Parcelable { * there are no more references to this bitmap. */ public void recycle() { - if (!mRecycled && mNativePtr != 0) { - if (nativeRecycle(mNativePtr)) { - // return value indicates whether native pixel object was actually recycled. - // false indicates that it is still in use at the native level and these - // objects should not be collected now. They will be collected later when the - // Bitmap itself is collected. - mNinePatchChunk = null; - } + if (!mRecycled) { + nativeRecycle(mNativePtr); + mNinePatchChunk = null; mRecycled = true; } } @@ -530,6 +552,7 @@ public final class Bitmap implements Parcelable { */ HARDWARE (7); + @UnsupportedAppUsage final int nativeInt; private static Config sConfigs[] = { @@ -540,6 +563,7 @@ public final class Bitmap implements Parcelable { this.nativeInt = ni; } + @UnsupportedAppUsage static Config nativeToConfig(int ni) { return sConfigs[ni]; } @@ -645,7 +669,10 @@ public final class Bitmap implements Parcelable { * setting the new bitmap's config to the one specified, and then copying * this bitmap's pixels into the new bitmap. If the conversion is not * supported, or the allocator fails, then this returns NULL. The returned - * bitmap has the same density and color space as the original. + * bitmap has the same density and color space as the original, except in + * the following cases. When copying to {@link Config#ALPHA_8}, the color + * space is dropped. When copying to or from {@link Config#RGBA_F16}, + * EXTENDED or non-EXTENDED variants may be adjusted as appropriate. * * @param config The desired config for the resulting bitmap * @param isMutable True if the resulting bitmap should be mutable (i.e. @@ -674,6 +701,7 @@ public final class Bitmap implements Parcelable { * * @hide */ + @UnsupportedAppUsage public Bitmap createAshmemBitmap() { checkRecycled("Can't copy a recycled bitmap"); noteHardwareBitmapSlowCall(); @@ -692,6 +720,7 @@ public final class Bitmap implements Parcelable { * * @hide */ + @UnsupportedAppUsage public Bitmap createAshmemBitmap(Config config) { checkRecycled("Can't copy a recycled bitmap"); noteHardwareBitmapSlowCall(); @@ -704,14 +733,45 @@ public final class Bitmap implements Parcelable { } /** - * Create hardware bitmap backed GraphicBuffer. + * Create a hardware bitmap backed by a {@link HardwareBuffer}. + * + * <p>The passed HardwareBuffer's usage flags must contain + * {@link HardwareBuffer#USAGE_GPU_SAMPLED_IMAGE}. * - * @return Bitmap or null if this GraphicBuffer has unsupported PixelFormat. - * currently PIXEL_FORMAT_RGBA_8888 is the only supported format + * <p>The bitmap will keep a reference to the buffer so that callers can safely close the + * HardwareBuffer without affecting the Bitmap. However the HardwareBuffer must not be + * modified while a wrapped Bitmap is accessing it. Doing so will result in undefined behavior. + * + * @param hardwareBuffer The HardwareBuffer to wrap. + * @param colorSpace The color space of the bitmap. Must be a {@link ColorSpace.Rgb} colorspace. + * If null, SRGB is assumed. + * @return A bitmap wrapping the buffer, or null if there was a problem creating the bitmap. + * @throws IllegalArgumentException if the HardwareBuffer has an invalid usage, or an invalid + * colorspace is given. + */ + @Nullable + public static Bitmap wrapHardwareBuffer(@NonNull HardwareBuffer hardwareBuffer, + @Nullable ColorSpace colorSpace) { + if ((hardwareBuffer.getUsage() & HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE) == 0) { + throw new IllegalArgumentException("usage flags must contain USAGE_GPU_SAMPLED_IMAGE."); + } + int format = hardwareBuffer.getFormat(); + if (colorSpace == null) { + colorSpace = ColorSpace.get(ColorSpace.Named.SRGB); + } + return nativeWrapHardwareBufferBitmap(hardwareBuffer, colorSpace.getNativeInstance()); + } + + /** + * Utility method to create a hardware backed bitmap using the graphics buffer. * @hide */ - public static Bitmap createHardwareBitmap(@NonNull GraphicBuffer graphicBuffer) { - return nativeCreateHardwareBitmap(graphicBuffer); + @Nullable + public static Bitmap wrapHardwareBuffer(@NonNull GraphicBuffer graphicBuffer, + @Nullable ColorSpace colorSpace) { + try (HardwareBuffer hb = HardwareBuffer.createFromGraphicBuffer(graphicBuffer)) { + return wrapHardwareBuffer(hb, colorSpace); + } } /** @@ -723,7 +783,13 @@ public final class Bitmap implements Parcelable { * @param src The source bitmap. * @param dstWidth The new bitmap's desired width. * @param dstHeight The new bitmap's desired height. - * @param filter true if the source should be filtered. + * @param filter Whether or not bilinear filtering should be used when scaling the + * bitmap. If this is true then bilinear filtering will be used when + * scaling which has better image quality at the cost of worse performance. + * If this is false then nearest-neighbor scaling is used instead which + * will have worse image quality but is faster. Recommended default + * is to set filter to 'true' as the cost of bilinear filtering is + * typically minimal and the improved image quality is significant. * @return The new scaled bitmap or the source bitmap if no scaling is required. * @throws IllegalArgumentException if width is <= 0, or height is <= 0 */ @@ -742,7 +808,7 @@ public final class Bitmap implements Parcelable { } /** - * Returns an immutable bitmap from the source bitmap. The new bitmap may + * Returns a bitmap from the source bitmap. The new bitmap may * be the same object as source, or a copy may have been made. It is * initialized with the same density and color space as the original bitmap. */ @@ -751,7 +817,7 @@ public final class Bitmap implements Parcelable { } /** - * Returns an immutable bitmap from the specified subset of the source + * Returns a bitmap from the specified subset of the source * bitmap. The new bitmap may be the same object as source, or a copy may * have been made. It is initialized with the same density and color space * as the original bitmap. @@ -771,7 +837,7 @@ public final class Bitmap implements Parcelable { } /** - * Returns an immutable bitmap from subset of the source bitmap, + * Returns a bitmap from subset of the source bitmap, * transformed by the optional matrix. The new bitmap may be the * same object as source, or a copy may have been made. It is * initialized with the same density and color space as the original @@ -781,6 +847,12 @@ public final class Bitmap implements Parcelable { * same as the source bitmap itself, then the source bitmap is * returned and no new bitmap is created. * + * The returned bitmap will always be mutable except in the following scenarios: + * (1) In situations where the source bitmap is returned and the source bitmap is immutable + * + * (2) The source bitmap is a hardware bitmap. That is {@link #getConfig()} is equivalent to + * {@link Config#HARDWARE} + * * @param source The bitmap we are subsetting * @param x The x coordinate of the first pixel in source * @param y The y coordinate of the first pixel in source @@ -793,7 +865,7 @@ public final class Bitmap implements Parcelable { * @return A bitmap that represents the specified subset of source * @throws IllegalArgumentException if the x, y, width, height values are * outside of the dimensions of the source bitmap, or width is <= 0, - * or height is <= 0 + * or height is <= 0, or if the source bitmap has already been recycled */ public static Bitmap createBitmap(@NonNull Bitmap source, int x, int y, int width, int height, @Nullable Matrix m, boolean filter) { @@ -806,6 +878,9 @@ public final class Bitmap implements Parcelable { if (y + height > source.getHeight()) { throw new IllegalArgumentException("y + height must be <= bitmap.height()"); } + if (source.isRecycled()) { + throw new IllegalArgumentException("cannot use a recycled source in createBitmap"); + } // check if we can just return our argument unchanged if (!source.isMutable() && x == 0 && y == 0 && width == source.getWidth() && @@ -851,8 +926,10 @@ public final class Bitmap implements Parcelable { } } + ColorSpace cs = source.getColorSpace(); + if (m == null || m.isIdentity()) { - bitmap = createBitmap(neww, newh, newConfig, source.hasAlpha()); + bitmap = createBitmap(null, neww, newh, newConfig, source.hasAlpha(), cs); paint = null; // not needed } else { final boolean transformed = !m.rectStaysRect(); @@ -866,9 +943,14 @@ public final class Bitmap implements Parcelable { if (transformed) { if (transformedConfig != Config.ARGB_8888 && transformedConfig != Config.RGBA_F16) { transformedConfig = Config.ARGB_8888; + if (cs == null) { + cs = ColorSpace.get(ColorSpace.Named.SRGB); + } } } - bitmap = createBitmap(neww, newh, transformedConfig, transformed || source.hasAlpha()); + + bitmap = createBitmap(null, neww, newh, transformedConfig, + transformed || source.hasAlpha(), cs); paint = new Paint(); paint.setFilterBitmap(filter); @@ -877,8 +959,6 @@ public final class Bitmap implements Parcelable { } } - nativeCopyColorSpace(source.mNativePtr, bitmap.mNativePtr); - // The new bitmap was created from a known bitmap source so assume that // they use the same density bitmap.mDensity = source.mDensity; @@ -960,10 +1040,10 @@ public final class Bitmap implements Parcelable { * @param hasAlpha If the bitmap is ARGB_8888 or RGBA_16F this flag can be used to * mark the bitmap as opaque. Doing so will clear the bitmap in black * instead of transparent. - * @param colorSpace The color space of the bitmap. If the config is {@link Config#RGBA_F16}, - * {@link ColorSpace.Named#EXTENDED_SRGB scRGB} is assumed, and if the - * config is not {@link Config#ARGB_8888}, {@link ColorSpace.Named#SRGB sRGB} - * is assumed. + * @param colorSpace The color space of the bitmap. If the config is {@link Config#RGBA_F16} + * and {@link ColorSpace.Named#SRGB sRGB} or + * {@link ColorSpace.Named#LINEAR_SRGB Linear sRGB} is provided then the + * corresponding extended range variant is assumed. * * @throws IllegalArgumentException if the width or height are <= 0, if * Config is Config.HARDWARE (because hardware bitmaps are always @@ -1015,10 +1095,10 @@ public final class Bitmap implements Parcelable { * @param hasAlpha If the bitmap is ARGB_8888 or RGBA_16F this flag can be used to * mark the bitmap as opaque. Doing so will clear the bitmap in black * instead of transparent. - * @param colorSpace The color space of the bitmap. If the config is {@link Config#RGBA_F16}, - * {@link ColorSpace.Named#EXTENDED_SRGB scRGB} is assumed, and if the - * config is not {@link Config#ARGB_8888}, {@link ColorSpace.Named#SRGB sRGB} - * is assumed. + * @param colorSpace The color space of the bitmap. If the config is {@link Config#RGBA_F16} + * and {@link ColorSpace.Named#SRGB sRGB} or + * {@link ColorSpace.Named#LINEAR_SRGB Linear sRGB} is provided then the + * corresponding extended range variant is assumed. * * @throws IllegalArgumentException if the width or height are <= 0, if * Config is Config.HARDWARE (because hardware bitmaps are always @@ -1035,30 +1115,12 @@ public final class Bitmap implements Parcelable { if (config == Config.HARDWARE) { throw new IllegalArgumentException("can't create mutable bitmap with Config.HARDWARE"); } - if (colorSpace == null) { + if (colorSpace == null && config != Config.ALPHA_8) { throw new IllegalArgumentException("can't create bitmap without a color space"); } - Bitmap bm; - // nullptr color spaces have a particular meaning in native and are interpreted as sRGB - // (we also avoid the unnecessary extra work of the else branch) - if (config != Config.ARGB_8888 || colorSpace == ColorSpace.get(ColorSpace.Named.SRGB)) { - bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true, null, null); - } else { - if (!(colorSpace instanceof ColorSpace.Rgb)) { - throw new IllegalArgumentException("colorSpace must be an RGB color space"); - } - ColorSpace.Rgb rgb = (ColorSpace.Rgb) colorSpace; - ColorSpace.Rgb.TransferParameters parameters = rgb.getTransferParameters(); - if (parameters == null) { - throw new IllegalArgumentException("colorSpace must use an ICC " - + "parametric transfer function"); - } - - ColorSpace.Rgb d50 = (ColorSpace.Rgb) ColorSpace.adapt(rgb, ColorSpace.ILLUMINANT_D50); - bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true, - d50.getTransform(), parameters); - } + Bitmap bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true, + colorSpace == null ? 0 : colorSpace.getNativeInstance()); if (display != null) { bm.mDensity = display.densityDpi; @@ -1136,8 +1198,9 @@ public final class Bitmap implements Parcelable { if (width <= 0 || height <= 0) { throw new IllegalArgumentException("width and height must be > 0"); } + ColorSpace sRGB = ColorSpace.get(ColorSpace.Named.SRGB); Bitmap bm = nativeCreate(colors, offset, stride, width, height, - config.nativeInt, false, null, null); + config.nativeInt, false, sRGB.getNativeInstance()); if (display != null) { bm.mDensity = display.densityDpi; } @@ -1218,11 +1281,9 @@ public final class Bitmap implements Parcelable { * scaled to match if necessary. * @param height The height of the bitmap to create. The picture's height will be * scaled to match if necessary. - * @param config The {@link Config} of the created bitmap. If this is null then - * the bitmap will be {@link Config#HARDWARE}. + * @param config The {@link Config} of the created bitmap. * - * @return An immutable bitmap with a HARDWARE config whose contents are created - * from the recorded drawing commands in the Picture source. + * @return An immutable bitmap with a configuration specified by the config parameter */ public static @NonNull Bitmap createBitmap(@NonNull Picture source, int width, int height, @NonNull Config config) { @@ -1240,13 +1301,14 @@ public final class Bitmap implements Parcelable { final RenderNode node = RenderNode.create("BitmapTemporary", null); node.setLeftTopRightBottom(0, 0, width, height); node.setClipToBounds(false); - final DisplayListCanvas canvas = node.start(width, height); + node.setForceDarkAllowed(false); + final RecordingCanvas canvas = node.beginRecording(width, height); if (source.getWidth() != width || source.getHeight() != height) { canvas.scale(width / (float) source.getWidth(), height / (float) source.getHeight()); } canvas.drawPicture(source); - node.end(canvas); + node.endRecording(); Bitmap bitmap = ThreadedRenderer.createHardwareBitmap(node, width, height); if (config != Config.HARDWARE) { bitmap = bitmap.copy(config, false); @@ -1261,7 +1323,7 @@ public final class Bitmap implements Parcelable { } canvas.drawPicture(source); canvas.setBitmap(null); - bitmap.makeImmutable(); + bitmap.setImmutable(); return bitmap; } } @@ -1352,13 +1414,22 @@ public final class Bitmap implements Parcelable { * Returns true if the bitmap is marked as mutable (i.e. can be drawn into) */ public final boolean isMutable() { - return mIsMutable; + return !nativeIsImmutable(mNativePtr); } - /** @hide */ - public final void makeImmutable() { - // todo mIsMutable = false; - // todo nMakeImmutable(); + /** + * Marks the Bitmap as immutable. Further modifications to this Bitmap are disallowed. + * After this method is called, this Bitmap cannot be made mutable again and subsequent calls + * to {@link #reconfigure(int, int, Config)}, {@link #setPixel(int, int, int)}, + * {@link #setPixels(int[], int, int, int, int, int, int)} and {@link #eraseColor(int)} will + * fail and throw an IllegalStateException. + * + * @hide + */ + public void setImmutable() { + if (isMutable()) { + nativeSetImmutable(mNativePtr); + } } /** @@ -1473,6 +1544,9 @@ public final class Bitmap implements Parcelable { * Convenience method that returns the width of this bitmap divided * by the density scale factor. * + * Returns the bitmap's width multiplied by the ratio of the target density to the bitmap's + * source density + * * @param targetDensity The density of the target canvas of the bitmap. * @return The scaled width of this bitmap, according to the density scale factor. */ @@ -1484,6 +1558,9 @@ public final class Bitmap implements Parcelable { * Convenience method that returns the height of this bitmap divided * by the density scale factor. * + * Returns the bitmap's height multiplied by the ratio of the target density to the bitmap's + * source density + * * @param targetDensity The density of the target canvas of the bitmap. * @return The scaled height of this bitmap, according to the density scale factor. */ @@ -1494,6 +1571,7 @@ public final class Bitmap implements Parcelable { /** * @hide */ + @UnsupportedAppUsage static public int scaleFromDensity(int size, int sdensity, int tdensity) { if (sdensity == DENSITY_NONE || tdensity == DENSITY_NONE || sdensity == tdensity) { return size; @@ -1658,41 +1736,79 @@ public final class Bitmap implements Parcelable { */ @Nullable public final ColorSpace getColorSpace() { - // A reconfigure can change the configuration and rgba16f is - // always linear scRGB at this time - if (getConfig() == Config.RGBA_F16) { - // Reset the color space for potential future reconfigurations - mColorSpace = null; - return ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB); + checkRecycled("getColorSpace called on a recycled bitmap"); + if (mColorSpace == null) { + mColorSpace = nativeComputeColorSpace(mNativePtr); } + return mColorSpace; + } - // Cache the color space retrieval since it can be fairly expensive - if (mColorSpace == null) { - if (nativeIsSRGB(mNativePtr)) { - mColorSpace = ColorSpace.get(ColorSpace.Named.SRGB); - } else if (getConfig() == Config.HARDWARE && nativeIsSRGBLinear(mNativePtr)) { - mColorSpace = ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB); + /** + * <p>Modifies the bitmap to have the specified {@link ColorSpace}, without + * affecting the underlying allocation backing the bitmap.</p> + * + * <p>This affects how the framework will interpret the color at each pixel. A bitmap + * with {@link Config#ALPHA_8} never has a color space, since a color space does not + * affect the alpha channel. Other {@code Config}s must always have a non-null + * {@code ColorSpace}.</p> + * + * @throws IllegalArgumentException If the specified color space is {@code null}, not + * {@link ColorSpace.Model#RGB RGB}, has a transfer function that is not an + * {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}, or whose + * components min/max values reduce the numerical range compared to the + * previously assigned color space. + * + * @throws IllegalArgumentException If the {@code Config} (returned by {@link #getConfig()}) + * is {@link Config#ALPHA_8}. + * + * @param colorSpace to assign to the bitmap + */ + public void setColorSpace(@NonNull ColorSpace colorSpace) { + checkRecycled("setColorSpace called on a recycled bitmap"); + if (colorSpace == null) { + throw new IllegalArgumentException("The colorSpace cannot be set to null"); + } + + if (getConfig() == Config.ALPHA_8) { + throw new IllegalArgumentException("Cannot set a ColorSpace on ALPHA_8"); + } + + // Keep track of the old ColorSpace for comparison, and so we can reset it in case of an + // Exception. + final ColorSpace oldColorSpace = getColorSpace(); + nativeSetColorSpace(mNativePtr, colorSpace.getNativeInstance()); + + // This will update mColorSpace. It may not be the same as |colorSpace|, e.g. if we + // corrected it because the Bitmap is F16. + mColorSpace = null; + final ColorSpace newColorSpace = getColorSpace(); + + try { + if (oldColorSpace.getComponentCount() != newColorSpace.getComponentCount()) { + throw new IllegalArgumentException("The new ColorSpace must have the same " + + "component count as the current ColorSpace"); } else { - float[] xyz = new float[9]; - float[] params = new float[7]; - - boolean hasColorSpace = nativeGetColorSpace(mNativePtr, xyz, params); - if (hasColorSpace) { - ColorSpace.Rgb.TransferParameters parameters = - new ColorSpace.Rgb.TransferParameters( - params[0], params[1], params[2], - params[3], params[4], params[5], params[6]); - ColorSpace cs = ColorSpace.match(xyz, parameters); - if (cs != null) { - mColorSpace = cs; - } else { - mColorSpace = new ColorSpace.Rgb("Unknown", xyz, parameters); + for (int i = 0; i < oldColorSpace.getComponentCount(); i++) { + if (oldColorSpace.getMinValue(i) < newColorSpace.getMinValue(i)) { + throw new IllegalArgumentException("The new ColorSpace cannot increase the " + + "minimum value for any of the components compared to the current " + + "ColorSpace. To perform this type of conversion create a new " + + "Bitmap in the desired ColorSpace and draw this Bitmap into it."); + } + if (oldColorSpace.getMaxValue(i) > newColorSpace.getMaxValue(i)) { + throw new IllegalArgumentException("The new ColorSpace cannot decrease the " + + "maximum value for any of the components compared to the current " + + "ColorSpace/ To perform this type of conversion create a new " + + "Bitmap in the desired ColorSpace and draw this Bitmap into it."); } } } + } catch (IllegalArgumentException e) { + // Undo the change to the ColorSpace. + mColorSpace = oldColorSpace; + nativeSetColorSpace(mNativePtr, mColorSpace.getNativeInstance()); + throw e; } - - return mColorSpace; } /** @@ -1709,6 +1825,25 @@ public final class Bitmap implements Parcelable { } /** + * Fills the bitmap's pixels with the specified {@code ColorLong}. + * + * @param color The color to fill as packed by the {@link Color} class. + * @throws IllegalStateException if the bitmap is not mutable. + * @throws IllegalArgumentException if the color space encoded in the + * {@code ColorLong} is invalid or unknown. + * + */ + public void eraseColor(@ColorLong long color) { + checkRecycled("Can't erase a recycled bitmap"); + if (!isMutable()) { + throw new IllegalStateException("cannot erase immutable bitmaps"); + } + + ColorSpace cs = Color.colorSpace(color); + nativeErase(mNativePtr, cs.getNativeInstance(), color); + } + + /** * Returns the {@link Color} at the specified location. Throws an exception * if x or y are out of bounds (negative or >= to the width or height * respectively). The returned color is a non-premultiplied ARGB value in @@ -1729,6 +1864,46 @@ public final class Bitmap implements Parcelable { return nativeGetPixel(mNativePtr, x, y); } + private static float clamp(float value, @NonNull ColorSpace cs, int index) { + return Math.max(Math.min(value, cs.getMaxValue(index)), cs.getMinValue(index)); + } + + /** + * Returns the {@link Color} at the specified location. Throws an exception + * if x or y are out of bounds (negative or >= to the width or height + * respectively). + * + * @param x The x coordinate (0...width-1) of the pixel to return + * @param y The y coordinate (0...height-1) of the pixel to return + * @return The {@link Color} at the specified coordinate + * @throws IllegalArgumentException if x, y exceed the bitmap's bounds + * @throws IllegalStateException if the bitmap's config is {@link Config#HARDWARE} + * + */ + @NonNull + public Color getColor(int x, int y) { + checkRecycled("Can't call getColor() on a recycled bitmap"); + checkHardware("unable to getColor(), " + + "pixel access is not supported on Config#HARDWARE bitmaps"); + checkPixelAccess(x, y); + + final ColorSpace cs = getColorSpace(); + if (cs.equals(ColorSpace.get(ColorSpace.Named.SRGB))) { + return Color.valueOf(nativeGetPixel(mNativePtr, x, y)); + } + // The returned value is in kRGBA_F16_SkColorType, which is packed as + // four half-floats, r,g,b,a. + long rgba = nativeGetColor(mNativePtr, x, y); + float r = Half.toFloat((short) ((rgba >> 0) & 0xffff)); + float g = Half.toFloat((short) ((rgba >> 16) & 0xffff)); + float b = Half.toFloat((short) ((rgba >> 32) & 0xffff)); + float a = Half.toFloat((short) ((rgba >> 48) & 0xffff)); + + // Skia may draw outside of the numerical range of the colorSpace. + // Clamp to get an expected value. + return Color.valueOf(clamp(r, cs, 0), clamp(g, cs, 1), clamp(b, cs, 2), a, cs); + } + /** * Returns in pixels[] a copy of the data in the bitmap. Each value is * a packed int representing a {@link Color}. The stride parameter allows @@ -1885,7 +2060,7 @@ public final class Bitmap implements Parcelable { x, y, width, height); } - public static final Parcelable.Creator<Bitmap> CREATOR + public static final @android.annotation.NonNull Parcelable.Creator<Bitmap> CREATOR = new Parcelable.Creator<Bitmap>() { /** * Rebuilds a bitmap previously stored with writeToParcel(). @@ -1924,7 +2099,7 @@ public final class Bitmap implements Parcelable { public void writeToParcel(Parcel p, int flags) { checkRecycled("Can't parcel a recycled bitmap"); noteHardwareBitmapSlowCall(); - if (!nativeWriteToParcel(mNativePtr, mIsMutable, mDensity, p)) { + if (!nativeWriteToParcel(mNativePtr, isMutable(), mDensity, p)) { throw new RuntimeException("native writeToParcel failed"); } } @@ -2022,10 +2197,15 @@ public final class Bitmap implements Parcelable { } /** - * * @return {@link GraphicBuffer} which is internally used by hardware bitmap + * + * Note: the GraphicBuffer does *not* have an associated {@link ColorSpace}. + * To render this object the same as its rendered with this Bitmap, you + * should also call {@link getColorSpace}. + * * @hide */ + @UnsupportedAppUsage public GraphicBuffer createGraphicBufferHandle() { return nativeCreateGraphicBufferHandle(mNativePtr); } @@ -2035,14 +2215,14 @@ public final class Bitmap implements Parcelable { private static native Bitmap nativeCreate(int[] colors, int offset, int stride, int width, int height, int nativeConfig, boolean mutable, - @Nullable @Size(9) float[] xyzD50, - @Nullable ColorSpace.Rgb.TransferParameters p); + long nativeColorSpace); private static native Bitmap nativeCopy(long nativeSrcBitmap, int nativeConfig, boolean isMutable); private static native Bitmap nativeCopyAshmem(long nativeSrcBitmap); private static native Bitmap nativeCopyAshmemConfig(long nativeSrcBitmap, int nativeConfig); private static native long nativeGetNativeFinalizer(); - private static native boolean nativeRecycle(long nativeBitmap); + private static native void nativeRecycle(long nativeBitmap); + @UnsupportedAppUsage private static native void nativeReconfigure(long nativeBitmap, int width, int height, int config, boolean isPremultiplied); @@ -2050,10 +2230,12 @@ public final class Bitmap implements Parcelable { int quality, OutputStream stream, byte[] tempStorage); private static native void nativeErase(long nativeBitmap, int color); + private static native void nativeErase(long nativeBitmap, long colorSpacePtr, long color); private static native int nativeRowBytes(long nativeBitmap); private static native int nativeConfig(long nativeBitmap); private static native int nativeGetPixel(long nativeBitmap, int x, int y); + private static native long nativeGetColor(long nativeBitmap, int x, int y); private static native void nativeGetPixels(long nativeBitmap, int[] pixels, int offset, int stride, int x, int y, int width, int height); @@ -2091,10 +2273,18 @@ public final class Bitmap implements Parcelable { private static native void nativePrepareToDraw(long nativeBitmap); private static native int nativeGetAllocationByteCount(long nativeBitmap); private static native Bitmap nativeCopyPreserveInternalConfig(long nativeBitmap); - private static native Bitmap nativeCreateHardwareBitmap(GraphicBuffer buffer); + private static native Bitmap nativeWrapHardwareBufferBitmap(HardwareBuffer buffer, + long nativeColorSpace); private static native GraphicBuffer nativeCreateGraphicBufferHandle(long nativeBitmap); - private static native boolean nativeGetColorSpace(long nativePtr, float[] xyz, float[] params); + private static native ColorSpace nativeComputeColorSpace(long nativePtr); + private static native void nativeSetColorSpace(long nativePtr, long nativeColorSpace); private static native boolean nativeIsSRGB(long nativePtr); private static native boolean nativeIsSRGBLinear(long nativePtr); - private static native void nativeCopyColorSpace(long srcBitmap, long dstBitmap); + + private static native void nativeSetImmutable(long nativePtr); + + // ---------------- @CriticalNative ------------------- + + @CriticalNative + private static native boolean nativeIsImmutable(long nativePtr); } diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java index 7ea35e73619a..5623a8a49b35 100644 --- a/graphics/java/android/graphics/BitmapFactory.java +++ b/graphics/java/android/graphics/BitmapFactory.java @@ -20,6 +20,7 @@ import static android.graphics.BitmapFactory.Options.validate; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UnsupportedAppUsage; import android.content.res.AssetManager; import android.content.res.Resources; import android.os.Trace; @@ -150,12 +151,9 @@ public class BitmapFactory { * the decoder will pick either the color space embedded in the image * or the color space best suited for the requested image configuration * (for instance {@link ColorSpace.Named#SRGB sRGB} for - * the {@link Bitmap.Config#ARGB_8888} configuration).</p> - * - * <p>{@link Bitmap.Config#RGBA_F16} always uses the - * {@link ColorSpace.Named#LINEAR_EXTENDED_SRGB scRGB} color space). - * Bitmaps in other configurations without an embedded color space are - * assumed to be in the {@link ColorSpace.Named#SRGB sRGB} color space.</p> + * {@link Bitmap.Config#ARGB_8888} configuration and + * {@link ColorSpace.Named#EXTENDED_SRGB EXTENDED_SRGB} for + * {@link Bitmap.Config#RGBA_F16}).</p> * * <p class="note">Only {@link ColorSpace.Model#RGB} color spaces are * currently supported. An <code>IllegalArgumentException</code> will @@ -438,8 +436,15 @@ public class BitmapFactory { static void validate(Options opts) { if (opts == null) return; - if (opts.inBitmap != null && opts.inBitmap.getConfig() == Bitmap.Config.HARDWARE) { - throw new IllegalArgumentException("Bitmaps with Config.HARWARE are always immutable"); + if (opts.inBitmap != null) { + if (opts.inBitmap.getConfig() == Bitmap.Config.HARDWARE) { + throw new IllegalArgumentException( + "Bitmaps with Config.HARDWARE are always immutable"); + } + if (opts.inBitmap.isRecycled()) { + throw new IllegalArgumentException( + "Cannot reuse a recycled Bitmap"); + } } if (opts.inMutable && opts.inPreferredConfig == Bitmap.Config.HARDWARE) { @@ -458,6 +463,32 @@ public class BitmapFactory { } } } + + /** + * Helper for passing inBitmap's native pointer to native. + */ + static long nativeInBitmap(Options opts) { + if (opts == null || opts.inBitmap == null) { + return 0; + } + + return opts.inBitmap.getNativeInstance(); + } + + /** + * Helper for passing SkColorSpace pointer to native. + * + * @throws IllegalArgumentException if the ColorSpace is not Rgb or does + * not have TransferParameters. + */ + static long nativeColorSpace(Options opts) { + if (opts == null || opts.inPreferredColorSpace == null) { + return 0; + } + + return opts.inPreferredColorSpace.getNativeInstance(); + } + } /** @@ -631,7 +662,9 @@ public class BitmapFactory { Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap"); try { - bm = nativeDecodeByteArray(data, offset, length, opts); + bm = nativeDecodeByteArray(data, offset, length, opts, + Options.nativeInBitmap(opts), + Options.nativeColorSpace(opts)); if (bm == null && opts != null && opts.inBitmap != null) { throw new IllegalArgumentException("Problem decoding into existing bitmap"); @@ -726,7 +759,8 @@ public class BitmapFactory { try { if (is instanceof AssetManager.AssetInputStream) { final long asset = ((AssetManager.AssetInputStream) is).getNativeAsset(); - bm = nativeDecodeAsset(asset, outPadding, opts); + bm = nativeDecodeAsset(asset, outPadding, opts, Options.nativeInBitmap(opts), + Options.nativeColorSpace(opts)); } else { bm = decodeStreamInternal(is, outPadding, opts); } @@ -753,7 +787,9 @@ public class BitmapFactory { byte [] tempStorage = null; if (opts != null) tempStorage = opts.inTempStorage; if (tempStorage == null) tempStorage = new byte[DECODE_BUFFER_SIZE]; - return nativeDecodeStream(is, tempStorage, outPadding, opts); + return nativeDecodeStream(is, tempStorage, outPadding, opts, + Options.nativeInBitmap(opts), + Options.nativeColorSpace(opts)); } /** @@ -796,7 +832,9 @@ public class BitmapFactory { Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeFileDescriptor"); try { if (nativeIsSeekable(fd)) { - bm = nativeDecodeFileDescriptor(fd, outPadding, opts); + bm = nativeDecodeFileDescriptor(fd, outPadding, opts, + Options.nativeInBitmap(opts), + Options.nativeColorSpace(opts)); } else { FileInputStream fis = new FileInputStream(fd); try { @@ -831,12 +869,17 @@ public class BitmapFactory { return decodeFileDescriptor(fd, null, null); } + @UnsupportedAppUsage private static native Bitmap nativeDecodeStream(InputStream is, byte[] storage, - Rect padding, Options opts); + Rect padding, Options opts, long inBitmapHandle, long colorSpaceHandle); + @UnsupportedAppUsage private static native Bitmap nativeDecodeFileDescriptor(FileDescriptor fd, - Rect padding, Options opts); - private static native Bitmap nativeDecodeAsset(long nativeAsset, Rect padding, Options opts); + Rect padding, Options opts, long inBitmapHandle, long colorSpaceHandle); + @UnsupportedAppUsage + private static native Bitmap nativeDecodeAsset(long nativeAsset, Rect padding, Options opts, + long inBitmapHandle, long colorSpaceHandle); + @UnsupportedAppUsage private static native Bitmap nativeDecodeByteArray(byte[] data, int offset, - int length, Options opts); + int length, Options opts, long inBitmapHandle, long colorSpaceHandle); private static native boolean nativeIsSeekable(FileDescriptor fd); } diff --git a/graphics/java/android/graphics/BitmapRegionDecoder.java b/graphics/java/android/graphics/BitmapRegionDecoder.java index 2da27c7dfdbf..629d8c131b68 100644 --- a/graphics/java/android/graphics/BitmapRegionDecoder.java +++ b/graphics/java/android/graphics/BitmapRegionDecoder.java @@ -15,7 +15,9 @@ package android.graphics; +import android.annotation.UnsupportedAppUsage; import android.content.res.AssetManager; +import android.os.Build; import java.io.FileDescriptor; import java.io.FileInputStream; @@ -165,6 +167,7 @@ public final class BitmapRegionDecoder { This can be called from JNI code. */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private BitmapRegionDecoder(long decoder) { mNativeBitmapRegionDecoder = decoder; mRecycled = false; @@ -192,7 +195,9 @@ public final class BitmapRegionDecoder { || rect.top >= getHeight()) throw new IllegalArgumentException("rectangle is outside the image"); return nativeDecodeRegion(mNativeBitmapRegionDecoder, rect.left, rect.top, - rect.right - rect.left, rect.bottom - rect.top, options); + rect.right - rect.left, rect.bottom - rect.top, options, + BitmapFactory.Options.nativeInBitmap(options), + BitmapFactory.Options.nativeColorSpace(options)); } } @@ -262,11 +267,13 @@ public final class BitmapRegionDecoder { private static native Bitmap nativeDecodeRegion(long lbm, int start_x, int start_y, int width, int height, - BitmapFactory.Options options); + BitmapFactory.Options options, long inBitmapHandle, + long colorSpaceHandle); private static native int nativeGetWidth(long lbm); private static native int nativeGetHeight(long lbm); private static native void nativeClean(long lbm); + @UnsupportedAppUsage private static native BitmapRegionDecoder nativeNewInstance( byte[] data, int offset, int length, boolean isShareable); private static native BitmapRegionDecoder nativeNewInstance( diff --git a/graphics/java/android/graphics/BitmapShader.java b/graphics/java/android/graphics/BitmapShader.java index 5577f53ee28b..198d1e7bc956 100644 --- a/graphics/java/android/graphics/BitmapShader.java +++ b/graphics/java/android/graphics/BitmapShader.java @@ -17,6 +17,7 @@ package android.graphics; import android.annotation.NonNull; +import android.annotation.UnsupportedAppUsage; /** * Shader used to draw a bitmap as a texture. The bitmap can be repeated or @@ -28,9 +29,12 @@ public class BitmapShader extends Shader { * @hide */ @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"}) + @UnsupportedAppUsage public Bitmap mBitmap; + @UnsupportedAppUsage private int mTileX; + @UnsupportedAppUsage private int mTileY; /** @@ -58,19 +62,9 @@ public class BitmapShader extends Shader { @Override long createNativeInstance(long nativeMatrix) { - return nativeCreate(nativeMatrix, mBitmap, mTileX, mTileY); + return nativeCreate(nativeMatrix, mBitmap.getNativeInstance(), mTileX, mTileY); } - /** - * @hide - */ - @Override - protected Shader copy() { - final BitmapShader copy = new BitmapShader(mBitmap, mTileX, mTileY); - copyLocalMatrix(copy); - return copy; - } - - private static native long nativeCreate(long nativeMatrix, Bitmap bitmap, + private static native long nativeCreate(long nativeMatrix, long bitmapHandle, int shaderTileModeX, int shaderTileModeY); } diff --git a/graphics/java/android/graphics/BlendMode.java b/graphics/java/android/graphics/BlendMode.java new file mode 100644 index 000000000000..5c294aa72ce8 --- /dev/null +++ b/graphics/java/android/graphics/BlendMode.java @@ -0,0 +1,588 @@ +/* + * Copyright (C) 2018 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.graphics; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +public enum BlendMode { + + /** + * {@usesMathJax} + * + * <p> + * <img src="{@docRoot}reference/android/images/graphics/blendmode_CLEAR.png" /> + * <figcaption>Destination pixels covered by the source are cleared to 0.</figcaption> + * </p> + * <p>\(\alpha_{out} = 0\)</p> + * <p>\(C_{out} = 0\)</p> + */ + CLEAR(0), + + /** + * {@usesMathJax} + * + * <p> + * <img src="{@docRoot}reference/android/images/graphics/blendmode_SRC.png" /> + * <figcaption>The source pixels replace the destination pixels.</figcaption> + * </p> + * <p>\(\alpha_{out} = \alpha_{src}\)</p> + * <p>\(C_{out} = C_{src}\)</p> + */ + SRC(1), + + /** + * {@usesMathJax} + * + * <p> + * <img src="{@docRoot}reference/android/images/graphics/blendmode_DST.png" /> + * <figcaption>The source pixels are discarded, leaving the destination intact.</figcaption> + * </p> + * <p>\(\alpha_{out} = \alpha_{dst}\)</p> + * <p>\(C_{out} = C_{dst}\)</p> + */ + DST(2), + + /** + * {@usesMathJax} + * + * <p> + * <img src="{@docRoot}reference/android/images/graphics/blendmode_SRC_OVER.png" /> + * <figcaption>The source pixels are drawn over the destination pixels.</figcaption> + * </p> + * <p>\(\alpha_{out} = \alpha_{src} + (1 - \alpha_{src}) * \alpha_{dst}\)</p> + * <p>\(C_{out} = C_{src} + (1 - \alpha_{src}) * C_{dst}\)</p> + */ + SRC_OVER(3), + + /** + * {@usesMathJax} + * + * <p> + * <img src="{@docRoot}reference/android/images/graphics/blendmode_DST_OVER.png" /> + * <figcaption>The source pixels are drawn behind the destination pixels.</figcaption> + * </p> + * <p>\(\alpha_{out} = \alpha_{dst} + (1 - \alpha_{dst}) * \alpha_{src}\)</p> + * <p>\(C_{out} = C_{dst} + (1 - \alpha_{dst}) * C_{src}\)</p> + */ + DST_OVER(4), + + /** + * {@usesMathJax} + * + * <p> + * <img src="{@docRoot}reference/android/images/graphics/blendmode_SRC_IN.png" /> + * <figcaption>Keeps the source pixels that cover the destination pixels, + * discards the remaining source and destination pixels.</figcaption> + * </p> + * <p>\(\alpha_{out} = \alpha_{src} * \alpha_{dst}\)</p> + * <p>\(C_{out} = C_{src} * \alpha_{dst}\)</p> + */ + SRC_IN(5), + + /** + * {@usesMathJax} + * + * <p> + * <img src="{@docRoot}reference/android/images/graphics/blendmode_DST_IN.png" /> + * <figcaption>Keeps the destination pixels that cover source pixels, + * discards the remaining source and destination pixels.</figcaption> + * </p> + * <p>\(\alpha_{out} = \alpha_{src} * \alpha_{dst}\)</p> + * <p>\(C_{out} = C_{dst} * \alpha_{src}\)</p> + */ + DST_IN(6), + + /** + * {@usesMathJax} + * + * <p> + * <img src="{@docRoot}reference/android/images/graphics/blendmode_SRC_OUT.png" /> + * <figcaption>Keeps the source pixels that do not cover destination pixels. + * Discards source pixels that cover destination pixels. Discards all + * destination pixels.</figcaption> + * </p> + * <p>\(\alpha_{out} = (1 - \alpha_{dst}) * \alpha_{src}\)</p> + * <p>\(C_{out} = (1 - \alpha_{dst}) * C_{src}\)</p> + */ + SRC_OUT(7), + + /** + * {@usesMathJax} + * + * <p> + * <img src="{@docRoot}reference/android/images/graphics/blendmode_DST_OUT.png" /> + * <figcaption>Keeps the destination pixels that are not covered by source pixels. + * Discards destination pixels that are covered by source pixels. Discards all + * source pixels.</figcaption> + * </p> + * <p>\(\alpha_{out} = (1 - \alpha_{src}) * \alpha_{dst}\)</p> + * <p>\(C_{out} = (1 - \alpha_{src}) * C_{dst}\)</p> + */ + DST_OUT(8), + + /** + * {@usesMathJax} + * + * <p> + * <img src="{@docRoot}reference/android/images/graphics/blendmode_SRC_ATOP.png" /> + * <figcaption>Discards the source pixels that do not cover destination pixels. + * Draws remaining source pixels over destination pixels.</figcaption> + * </p> + * <p>\(\alpha_{out} = \alpha_{dst}\)</p> + * <p>\(C_{out} = \alpha_{dst} * C_{src} + (1 - \alpha_{src}) * C_{dst}\)</p> + */ + SRC_ATOP(9), + + /** + * {@usesMathJax} + * + * <p> + * <img src="{@docRoot}reference/android/images/graphics/blendmode_DST_ATOP.png" /> + * <figcaption>Discards the destination pixels that are not covered by source pixels. + * Draws remaining destination pixels over source pixels.</figcaption> + * </p> + * <p>\(\alpha_{out} = \alpha_{src}\)</p> + * <p>\(C_{out} = \alpha_{src} * C_{dst} + (1 - \alpha_{dst}) * C_{src}\)</p> + */ + DST_ATOP(10), + + /** + * {@usesMathJax} + * + * <p> + * <img src="{@docRoot}reference/android/images/graphics/blendmode_XOR.png" /> + * <figcaption>Discards the source and destination pixels where source pixels + * cover destination pixels. Draws remaining source pixels.</figcaption> + * </p> + * <p> + * \(\alpha_{out} = (1 - \alpha_{dst}) * \alpha_{src} + (1 - \alpha_{src}) * \alpha_{dst}\) + * </p> + * <p>\(C_{out} = (1 - \alpha_{dst}) * C_{src} + (1 - \alpha_{src}) * C_{dst}\)</p> + */ + XOR(11), + + /** + * {@usesMathJax} + * + * <p> + * <img src="{@docRoot}reference/android/images/graphics/blendmode_PLUS.png" /> + * <figcaption>Adds the source pixels to the destination pixels and saturates + * the result.</figcaption> + * </p> + * <p>\(\alpha_{out} = max(0, min(\alpha_{src} + \alpha_{dst}, 1))\)</p> + * <p>\(C_{out} = max(0, min(C_{src} + C_{dst}, 1))\)</p> + */ + PLUS(12), + + /** + * {@usesMathJax} + * + * <p> + * <img src="{@docRoot}reference/android/images/graphics/blendmode_MODULATE.png" /> + * <figcaption>Multiplies the source and destination pixels.</figcaption> + * </p> + * <p>\(\alpha_{out} = \alpha_{src} * \alpha_{dst}\)</p> + * <p>\(C_{out} = C_{src} * C_{dst}\)</p> + * + */ + MODULATE(13), + + /** + * {@usesMathJax} + * + * <p> + * <img src="{@docRoot}reference/android/images/graphics/blendmode_SCREEN.png" /> + * <figcaption> + * Adds the source and destination pixels, then subtracts the + * source pixels multiplied by the destination. + * </figcaption> + * </p> + * <p>\(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)</p> + * <p>\(C_{out} = C_{src} + C_{dst} - C_{src} * C_{dst}\)</p> + */ + SCREEN(14), + + /** + * {@usesMathJax} + * + * <p> + * <img src="{@docRoot}reference/android/images/graphics/blendmode_OVERLAY.png" /> + * <figcaption> + * Multiplies or screens the source and destination depending on the + * destination color. + * </figcaption> + * </p> + * <p>\(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)</p> + * <p>\(\begin{equation} + * C_{out} = \begin{cases} 2 * C_{src} * C_{dst} & 2 * C_{dst} \lt \alpha_{dst} \\ + * \alpha_{src} * \alpha_{dst} - 2 (\alpha_{dst} - C_{src}) (\alpha_{src} - C_{dst}) & + * otherwise \end{cases} + * \end{equation}\)</p> + */ + OVERLAY(15), + + /** + * {@usesMathJax} + * + * <p> + * <img src="{@docRoot}reference/android/images/graphics/blendmode_DARKEN.png" /> + * <figcaption> + * Retains the smallest component of the source and + * destination pixels. + * </figcaption> + * </p> + * <p>\(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)</p> + * <p> + * \(C_{out} = + * (1 - \alpha_{dst}) * C_{src} + (1 - \alpha_{src}) * C_{dst} + min(C_{src}, C_{dst})\) + * </p> + */ + DARKEN(16), + + /** + * {@usesMathJax} + * + * <p> + * <img src="{@docRoot}reference/android/images/graphics/blendmode_LIGHTEN.png" /> + * <figcaption>Retains the largest component of the source and + * destination pixel.</figcaption> + * </p> + * <p>\(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)</p> + * <p> + * \(C_{out} = + * (1 - \alpha_{dst}) * C_{src} + (1 - \alpha_{src}) * C_{dst} + max(C_{src}, C_{dst})\) + * </p> + */ + LIGHTEN(17), + + /** + * {@usesMathJax} + * + * <p> + * <img src="{@docRoot}reference/android/images/graphics/blendmode_COLOR_DODGE.png" /> + * <figcaption>Makes destination brighter to reflect source.</figcaption> + * </p> + * <p> + * \(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\) + * </p> + * <p> + * \begin{equation} + * C_{out} = + * \begin{cases} + * C_{src} * (1 - \alpha_{dst}) & C_{dst} = 0 \\ + * C_{src} + \alpha_{dst}*(1 - \alpha_{src}) & C_{src} = \alpha_{src} \\ + * \alpha_{src} * min(\alpha_{dst}, C_{dst} * \alpha_{src}/(\alpha_{src} - C_{src})) + * + C_{src} *(1 - \alpha_{dst} + \alpha_{dst}*(1 - \alpha_{src}) & otherwise + * \end{cases} + * \end{equation} + * </p> + */ + COLOR_DODGE(18), + + /** + * {@usesMathJax} + * + * <p> + * <img src="{@docRoot}reference/android/images/graphics/blendmode_COLOR_BURN.png" /> + * <figcaption>Makes destination darker to reflect source.</figcaption> + * </p> + * <p> + * \(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\) + * </p> + * <p> + * \begin{equation} + * C_{out} = + * \begin{cases} + * C_{dst} + C_{src}*(1 - \alpha_{dst}) & C_{dst} = \alpha_{dst} \\ + * \alpha_{dst}*(1 - \alpha_{src}) & C_{src} = 0 \\ + * \alpha_{src}*(\alpha_{dst} - min(\alpha_{dst}, (\alpha_{dst} + * - C_{dst})*\alpha_{src}/C_{src})) + * + C_{src} * (1 - \alpha_{dst}) + \alpha_{dst}*(1-\alpha_{src}) & otherwise + * \end{cases} + * \end{equation} + * </p> + */ + COLOR_BURN(19), + + /** + * {@usesMathJax} + * + * <p> + * <img src="{@docRoot}reference/android/images/graphics/blendmode_HARD_LIGHT.png" /> + * <figcaption>Makes destination lighter or darker, depending on source.</figcaption> + * </p> + * <p> + * \(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\) + * </p> + * <p> + * \begin{equation} + * C_{out} = + * \begin{cases} + * 2*C_{src}*C_{dst} & C_{src}*(1-\alpha_{dst}) + C_{dst}*(1-\alpha_{src}) + 2*C_{src} + * \leq \alpha_{src} \\ + * \alpha_{src}*\alpha_{dst}- 2*(\alpha_{dst} - C_{dst})*(\alpha_{src} - C_{src}) + * & otherwise + * \end{cases} + * \end{equation} + * </p> + */ + HARD_LIGHT(20), + + /** + * {@usesMathJax} + * + * <p> + * <img src="{@docRoot}reference/android/images/graphics/blendmode_SOFT_LIGHT.png" /> + * <figcaption>Makes destination lighter or darker, depending on source.</figcaption> + * </p> + * <p> + * Where + * \begin{equation} + * m = + * \begin{cases} + * C_{dst} / \alpha_{dst} & \alpha_{dst} \gt 0 \\ + * 0 & otherwise + * \end{cases} + * \end{equation} + * </p> + * <p> + * \begin{equation} + * g = + * \begin{cases} + * (16 * m * m + 4 * m) * (m - 1) + 7 * m & 4 * C_{dst} \leq \alpha_{dst} \\ + * \sqrt m - m & otherwise + * \end{cases} + * \end{equation} + * </p> + * <p> + * \begin{equation} + * f = + * \begin{cases} + * C_{dst} * (\alpha_{src} + (2 * C_{src} - \alpha_{src}) * (1 - m)) + * & 2 * C_{src} \leq \alpha_{src} \\ + * C_{dst} * \alpha_{src} + \alpha_{dst} * (2 * C_{src} - \alpha_{src}) * g + * & otherwise + * \end{cases} + * \end{equation} + * </p> + * <p> + * \begin{equation} + * \alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst} + * \end{equation} + * \begin{equation} + * C_{out} = C_{src} / \alpha_{dst} + C_{dst} / \alpha_{src} + f + * \end{equation} + * </p> + */ + SOFT_LIGHT(21), + + /** + * {@usesMathJax} + * + * <p> + * <img src="{@docRoot}reference/android/images/graphics/blendmode_DIFFERENCE.png" /> + * <figcaption>Subtracts darker from lighter with higher contrast.</figcaption> + * </p> + * <p> + * \begin{equation} + * \alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst} + * \end{equation} + * </p> + * <p> + * \begin{equation} + * C_{out} = C_{src} + C_{dst} - 2 * min(C_{src} + * * \alpha_{dst}, C_{dst} * \alpha_{src}) + * \end{equation} + * </p> + */ + DIFFERENCE(22), + + /** + * {@usesMathJax} + * + * <p> + * <img src="{@docRoot}reference/android/images/graphics/blendmode_DIFFERENCE.png" /> + * <figcaption>Subtracts darker from lighter with lower contrast.</figcaption> + * </p> + * <p> + * \begin{equation} + * \alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst} + * \end{equation} + * </p> + * <p> + * \begin{equation} + * C_{out} = C_{src} + C_{dst} - 2 * C_{src} * C_{dst} + * \end{equation} + * </p> + */ + EXCLUSION(23), + + /** + * {@usesMathJax} + * + * <p> + * <img src="{@docRoot}reference/android/images/graphics/blendmode_MODULATE.png" /> + * <figcaption>Multiplies the source and destination pixels.</figcaption> + * </p> + * <p>\(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)</p> + * <p>\(C_{out} = + * C_{src} * (1 - \alpha_{dst}) + C_{dst} * (1 - \alpha_{src}) + (C_{src} * C_{dst})\) + * </p> + */ + MULTIPLY(24), + + /** + * {@usesMathJax} + * + * <p> + * <img src="{@docRoot}reference/android/images/graphics/blendmode_HUE.png" /> + * <figcaption> + * Replaces hue of destination with hue of source, leaving saturation + * and luminosity unchanged. + * </figcaption> + * </p> + */ + HUE(25), + + /** + * {@usesMathJax} + * + * <p> + * <img src="{@docRoot}reference/android/images/graphics/blendmode_SATURATION.png" /> + * <figcaption> + * Replaces saturation of destination saturation hue of source, leaving hue and + * luminosity unchanged. + * </figcaption> + * </p> + */ + SATURATION(26), + + /** + * {@usesMathJax} + * + * <p> + * <img src="{@docRoot}reference/android/images/graphics/blendmode_COLOR.png" /> + * <figcaption> + * Replaces hue and saturation of destination with hue and saturation of source, + * leaving luminosity unchanged. + * </figcaption> + * </p> + */ + COLOR(27), + + /** + * {@usesMathJax} + * + * <p> + * <img src="{@docRoot}reference/android/images/graphics/blendmode_LUMINOSITY.png" /> + * <figcaption> + * Replaces luminosity of destination with luminosity of source, leaving hue and + * saturation unchanged. + * </figcaption> + * </p> + */ + LUMINOSITY(28); + + private static final BlendMode[] BLEND_MODES = values(); + + /** + * @hide + */ + public static @Nullable BlendMode fromValue(int value) { + for (BlendMode mode : BLEND_MODES) { + if (mode.mXfermode.porterDuffMode == value) { + return mode; + } + } + return null; + } + + /** + * @hide + */ + public static int toValue(BlendMode mode) { + return mode.getXfermode().porterDuffMode; + } + + /** + * @hide + */ + public static @Nullable PorterDuff.Mode blendModeToPorterDuffMode(@Nullable BlendMode mode) { + if (mode != null) { + switch (mode) { + case CLEAR: + return PorterDuff.Mode.CLEAR; + case SRC: + return PorterDuff.Mode.SRC; + case DST: + return PorterDuff.Mode.DST; + case SRC_OVER: + return PorterDuff.Mode.SRC_OVER; + case DST_OVER: + return PorterDuff.Mode.DST_OVER; + case SRC_IN: + return PorterDuff.Mode.SRC_IN; + case DST_IN: + return PorterDuff.Mode.DST_IN; + case SRC_OUT: + return PorterDuff.Mode.SRC_OUT; + case DST_OUT: + return PorterDuff.Mode.DST_OUT; + case SRC_ATOP: + return PorterDuff.Mode.SRC_ATOP; + case DST_ATOP: + return PorterDuff.Mode.DST_ATOP; + case XOR: + return PorterDuff.Mode.XOR; + case DARKEN: + return PorterDuff.Mode.DARKEN; + case LIGHTEN: + return PorterDuff.Mode.LIGHTEN; + // b/73224934 PorterDuff Multiply maps to Skia Modulate + case MODULATE: + return PorterDuff.Mode.MULTIPLY; + case SCREEN: + return PorterDuff.Mode.SCREEN; + case PLUS: + return PorterDuff.Mode.ADD; + case OVERLAY: + return PorterDuff.Mode.OVERLAY; + default: + return null; + } + } else { + return null; + } + } + + @NonNull + private final Xfermode mXfermode; + + BlendMode(int mode) { + mXfermode = new Xfermode(); + mXfermode.porterDuffMode = mode; + } + + /** + * @hide + */ + @NonNull + public Xfermode getXfermode() { + return mXfermode; + } +} diff --git a/graphics/java/android/graphics/BlendModeColorFilter.java b/graphics/java/android/graphics/BlendModeColorFilter.java new file mode 100644 index 000000000000..7caeb4267ad2 --- /dev/null +++ b/graphics/java/android/graphics/BlendModeColorFilter.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2018 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.graphics; + +import android.annotation.ColorInt; +import android.annotation.NonNull; + +/** + * A color filter that can be used to tint the source pixels using a single + * color and a specific {@link BlendMode}. + */ +public final class BlendModeColorFilter extends ColorFilter { + + @ColorInt final int mColor; + private final BlendMode mMode; + + public BlendModeColorFilter(@ColorInt int color, @NonNull BlendMode mode) { + mColor = color; + mMode = mode; + } + + + /** + * Returns the ARGB color used to tint the source pixels when this filter + * is applied. + * + * @see Color + * + */ + @ColorInt + public int getColor() { + return mColor; + } + + /** + * Returns the Porter-Duff mode used to composite this color filter's + * color with the source pixel when this filter is applied. + * + * @see BlendMode + * + */ + public BlendMode getMode() { + return mMode; + } + + @Override + long createNativeInstance() { + return native_CreateBlendModeFilter(mColor, mMode.getXfermode().porterDuffMode); + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null || getClass() != object.getClass()) { + return false; + } + final BlendModeColorFilter other = (BlendModeColorFilter) object; + return other.mMode == mMode; + } + + @Override + public int hashCode() { + return 31 * mMode.hashCode() + mColor; + } + + private static native long native_CreateBlendModeFilter(int srcColor, int blendmode); + +} diff --git a/graphics/java/android/graphics/Camera.java b/graphics/java/android/graphics/Camera.java index 60588d07196c..cbd4eadca30a 100644 --- a/graphics/java/android/graphics/Camera.java +++ b/graphics/java/android/graphics/Camera.java @@ -16,14 +16,14 @@ package android.graphics; +import android.annotation.UnsupportedAppUsage; + /** * A camera instance can be used to compute 3D transformations and * generate a matrix that can be applied, for instance, on a * {@link Canvas}. */ public class Camera { - private Matrix mMatrix; - /** * Creates a new camera, with empty transformations. */ @@ -149,13 +149,7 @@ public class Camera { * @param canvas The Canvas to set the transform matrix onto */ public void applyToCanvas(Canvas canvas) { - if (canvas.isHardwareAccelerated()) { - if (mMatrix == null) mMatrix = new Matrix(); - getMatrix(mMatrix); - canvas.concat(mMatrix); - } else { - nativeApplyToCanvas(canvas.getNativeCanvasWrapper()); - } + nativeApplyToCanvas(canvas.getNativeCanvasWrapper()); } public native float dotWithNormal(float dx, float dy, float dz); @@ -174,5 +168,6 @@ public class Camera { private native void nativeGetMatrix(long native_matrix); private native void nativeApplyToCanvas(long native_canvas); + @UnsupportedAppUsage long native_instance; } diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java index 22c4e7557b78..a815f20293c5 100644 --- a/graphics/java/android/graphics/Canvas.java +++ b/graphics/java/android/graphics/Canvas.java @@ -17,10 +17,13 @@ package android.graphics; import android.annotation.ColorInt; +import android.annotation.ColorLong; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Size; +import android.annotation.UnsupportedAppUsage; +import android.graphics.text.MeasuredText; import android.os.Build; import dalvik.annotation.optimization.CriticalNative; @@ -54,6 +57,7 @@ public class Canvas extends BaseCanvas { public static boolean sCompatibilitySetBitmap = false; /** @hide */ + @UnsupportedAppUsage public long getNativeCanvasWrapper() { return mNativeCanvasWrapper; } @@ -62,6 +66,7 @@ public class Canvas extends BaseCanvas { public boolean isRecordingFor(Object o) { return false; } // may be null + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 117521088) private Bitmap mBitmap; // optional field set by the caller @@ -71,14 +76,11 @@ public class Canvas extends BaseCanvas { // (see SkCanvas.cpp, SkDraw.cpp) private static final int MAXMIMUM_BITMAP_SIZE = 32766; - // The approximate size of the native allocation associated with - // a Canvas object. - private static final long NATIVE_ALLOCATION_SIZE = 525; - // Use a Holder to allow static initialization of Canvas in the boot image. private static class NoImagePreloadHolder { - public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( - Canvas.class.getClassLoader(), nGetNativeFinalizer(), NATIVE_ALLOCATION_SIZE); + public static final NativeAllocationRegistry sRegistry = + NativeAllocationRegistry.createMalloced( + Canvas.class.getClassLoader(), nGetNativeFinalizer()); } // This field is used to finalize the native Canvas properly @@ -93,7 +95,7 @@ public class Canvas extends BaseCanvas { public Canvas() { if (!isHardwareAccelerated()) { // 0 means no native bitmap - mNativeCanvasWrapper = nInitRaster(null); + mNativeCanvasWrapper = nInitRaster(0); mFinalizer = NoImagePreloadHolder.sRegistry.registerNativeAllocation( this, mNativeCanvasWrapper); } else { @@ -115,7 +117,7 @@ public class Canvas extends BaseCanvas { throw new IllegalStateException("Immutable bitmap passed to Canvas constructor"); } throwIfCannotDraw(bitmap); - mNativeCanvasWrapper = nInitRaster(bitmap); + mNativeCanvasWrapper = nInitRaster(bitmap.getNativeInstance()); mFinalizer = NoImagePreloadHolder.sRegistry.registerNativeAllocation( this, mNativeCanvasWrapper); mBitmap = bitmap; @@ -123,6 +125,7 @@ public class Canvas extends BaseCanvas { } /** @hide */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public Canvas(long nativeCanvas) { if (nativeCanvas == 0) { throw new IllegalStateException(); @@ -141,6 +144,7 @@ public class Canvas extends BaseCanvas { * @hide */ @Deprecated + @UnsupportedAppUsage protected GL getGL() { return null; } @@ -181,7 +185,7 @@ public class Canvas extends BaseCanvas { } if (bitmap == null) { - nSetBitmap(mNativeCanvasWrapper, null); + nSetBitmap(mNativeCanvasWrapper, 0); mDensity = Bitmap.DENSITY_NONE; } else { if (!bitmap.isMutable()) { @@ -189,7 +193,7 @@ public class Canvas extends BaseCanvas { } throwIfCannotDraw(bitmap); - nSetBitmap(mNativeCanvasWrapper, bitmap); + nSetBitmap(mNativeCanvasWrapper, bitmap.getNativeInstance()); mDensity = bitmap.mDensity; } @@ -200,11 +204,70 @@ public class Canvas extends BaseCanvas { mBitmap = bitmap; } - /** @hide */ - public void insertReorderBarrier() {} + /** + * @deprecated use {@link #enableZ()} instead + * @hide */ + @Deprecated + public void insertReorderBarrier() { + enableZ(); + } - /** @hide */ - public void insertInorderBarrier() {} + /** + * @deprecated use {@link #disableZ()} instead + * @hide */ + @Deprecated + public void insertInorderBarrier() { + disableZ(); + } + + /** + * <p>Enables Z support which defaults to disabled. This allows for RenderNodes drawn with + * {@link #drawRenderNode(RenderNode)} to be re-arranged based off of their + * {@link RenderNode#getElevation()} and {@link RenderNode#getTranslationZ()} + * values. It also enables rendering of shadows for RenderNodes with an elevation or + * translationZ.</p> + * + * <p>Any draw reordering will not be moved before this call. A typical usage of this might + * look something like: + * + * <pre class="prettyprint"> + * void draw(Canvas canvas) { + * // Draw any background content + * canvas.drawColor(backgroundColor); + * + * // Begin drawing that may be reordered based off of Z + * canvas.enableZ(); + * for (RenderNode child : children) { + * canvas.drawRenderNode(child); + * } + * // End drawing that may be reordered based off of Z + * canvas.disableZ(); + * + * // Draw any overlays + * canvas.drawText("I'm on top of everything!", 0, 0, paint); + * } + * </pre> + * </p> + * + * Note: This is not impacted by any {@link #save()} or {@link #restore()} calls as it is not + * considered to be part of the current matrix or clip. + * + * See {@link #disableZ()} + */ + public void enableZ() { + } + + /** + * Disables Z support, preventing any RenderNodes drawn after this point from being + * visually reordered or having shadows rendered. + * + * Note: This is not impacted by any {@link #save()} or {@link #restore()} calls as it is not + * considered to be part of the current matrix or clip. + * + * See {@link #enableZ()} + */ + public void disableZ() { + } /** * Return true if the device that the current layer draws into is opaque @@ -269,6 +332,7 @@ public class Canvas extends BaseCanvas { } /** @hide */ + @UnsupportedAppUsage public void setScreenDensity(int density) { mScreenDensity = density; } @@ -491,7 +555,17 @@ public class Canvas extends BaseCanvas { * @hide */ public int saveUnclippedLayer(int left, int top, int right, int bottom) { - return nSaveLayer(mNativeCanvasWrapper, left, top, right, bottom, 0, 0); + return nSaveUnclippedLayer(mNativeCanvasWrapper, left, top, right, bottom); + } + + /** + * @hide + * @param saveCount The save level to restore to. + * @param paint This is copied and is applied to the area within the unclipped layer's + * bounds (i.e. equivalent to a drawPaint()) before restore() is called. + */ + public void restoreUnclippedLayer(int saveCount, Paint paint) { + nRestoreUnclippedLayer(mNativeCanvasWrapper, saveCount, paint.getNativeInstance()); } /** @@ -1263,6 +1337,7 @@ public class Canvas extends BaseCanvas { * * @hide */ + @UnsupportedAppUsage public void release() { mNativeCanvasWrapper = 0; if (mFinalizer != null) { @@ -1276,6 +1351,7 @@ public class Canvas extends BaseCanvas { * * @hide */ + @UnsupportedAppUsage public static void freeCaches() { nFreeCaches(); } @@ -1285,6 +1361,7 @@ public class Canvas extends BaseCanvas { * * @hide */ + @UnsupportedAppUsage public static void freeTextLayoutCaches() { nFreeTextLayoutCaches(); } @@ -1297,14 +1374,16 @@ public class Canvas extends BaseCanvas { private static native void nFreeCaches(); private static native void nFreeTextLayoutCaches(); - private static native long nInitRaster(Bitmap bitmap); private static native long nGetNativeFinalizer(); private static native void nSetCompatibilityVersion(int apiLevel); // ---------------- @FastNative ------------------- @FastNative - private static native void nSetBitmap(long canvasHandle, Bitmap bitmap); + private static native long nInitRaster(long bitmapHandle); + + @FastNative + private static native void nSetBitmap(long canvasHandle, long bitmapHandle); @FastNative private static native boolean nGetClipBounds(long nativeCanvas, Rect bounds); @@ -1327,6 +1406,11 @@ public class Canvas extends BaseCanvas { private static native int nSaveLayerAlpha(long nativeCanvas, float l, float t, float r, float b, int alpha, int layerFlags); @CriticalNative + private static native int nSaveUnclippedLayer(long nativeCanvas, int l, int t, int r, int b); + @CriticalNative + private static native void nRestoreUnclippedLayer(long nativeCanvas, int saveCount, + long nativePaint); + @CriticalNative private static native boolean nRestore(long canvasHandle); @CriticalNative private static native void nRestoreToCount(long canvasHandle, int saveCount); @@ -1611,10 +1695,23 @@ public class Canvas extends BaseCanvas { } /** + * Fill the entire canvas' bitmap (restricted to the current clip) with the specified color, + * using srcover porterduff mode. + * + * @param color the {@code ColorLong} to draw onto the canvas. See the {@link Color} + * class for details about {@code ColorLong}s. + * @throws IllegalArgumentException if the color space encoded in the {@code ColorLong} + * is invalid or unknown. + */ + public void drawColor(@ColorLong long color) { + super.drawColor(color, BlendMode.SRC_OVER); + } + + /** * Fill the entire canvas' bitmap (restricted to the current clip) with the specified color and * porter-duff xfermode. * - * @param color the color to draw with + * @param color the color to draw onto the canvas * @param mode the porter-duff mode to apply to the color */ public void drawColor(@ColorInt int color, @NonNull PorterDuff.Mode mode) { @@ -1622,6 +1719,31 @@ public class Canvas extends BaseCanvas { } /** + * Fill the entire canvas' bitmap (restricted to the current clip) with the specified color and + * blendmode. + * + * @param color the color to draw onto the canvas + * @param mode the blendmode to apply to the color + */ + public void drawColor(@ColorInt int color, @NonNull BlendMode mode) { + super.drawColor(color, mode); + } + + /** + * Fill the entire canvas' bitmap (restricted to the current clip) with the specified color and + * blendmode. + * + * @param color the {@code ColorLong} to draw onto the canvas. See the {@link Color} + * class for details about {@code ColorLong}s. + * @param mode the blendmode to apply to the color + * @throws IllegalArgumentException if the color space encoded in the {@code ColorLong} + * is invalid or unknown. + */ + public void drawColor(@ColorLong long color, @NonNull BlendMode mode) { + super.drawColor(color, mode); + } + + /** * Draw a line segment with the specified start and stop x,y coordinates, using the specified * paint. * <p> @@ -1868,6 +1990,51 @@ public class Canvas extends BaseCanvas { } /** + * Draws a double rounded rectangle using the specified paint. The resultant round rect + * will be filled in the area defined between the outer and inner rectangular bounds if + * the {@link Paint} configured with {@link Paint.Style#FILL}. + * Otherwise if {@link Paint.Style#STROKE} is used, then 2 rounded rect strokes will + * be drawn at the outer and inner rounded rectangles + * + * @param outer The outer rectangular bounds of the roundRect to be drawn + * @param outerRx The x-radius of the oval used to round the corners on the outer rectangle + * @param outerRy The y-radius of the oval used to round the corners on the outer rectangle + * @param inner The inner rectangular bounds of the roundRect to be drawn + * @param innerRx The x-radius of the oval used to round the corners on the inner rectangle + * @param innerRy The y-radius of the oval used to round the corners on the outer rectangle + * @param paint The paint used to draw the double roundRect + */ + @Override + public void drawDoubleRoundRect(@NonNull RectF outer, float outerRx, float outerRy, + @NonNull RectF inner, float innerRx, float innerRy, @NonNull Paint paint) { + super.drawDoubleRoundRect(outer, outerRx, outerRy, inner, innerRx, innerRy, paint); + } + + /** + * Draws a double rounded rectangle using the specified paint. The resultant round rect + * will be filled in the area defined between the outer and inner rectangular bounds if + * the {@link Paint} configured with {@link Paint.Style#FILL}. + * Otherwise if {@link Paint.Style#STROKE} is used, then 2 rounded rect strokes will + * be drawn at the outer and inner rounded rectangles + * + * @param outer The outer rectangular bounds of the roundRect to be drawn + * @param outerRadii Array of 8 float representing the x, y corner radii for top left, + * top right, bottom right, bottom left corners respectively on the outer + * rounded rectangle + * + * @param inner The inner rectangular bounds of the roundRect to be drawn + * @param innerRadii Array of 8 float representing the x, y corner radii for top left, + * top right, bottom right, bottom left corners respectively on the + * outer rounded rectangle + * @param paint The paint used to draw the double roundRect + */ + @Override + public void drawDoubleRoundRect(@NonNull RectF outer, @NonNull float[] outerRadii, + @NonNull RectF inner, @NonNull float[] innerRadii, @NonNull Paint paint) { + super.drawDoubleRoundRect(outer, outerRadii, inner, innerRadii, paint); + } + + /** * Draw the text, with origin at (x,y), using the specified paint. The origin is interpreted * based on the Align setting in the paint. * @@ -1928,9 +2095,11 @@ public class Canvas extends BaseCanvas { /** * Draw the text, with origin at (x,y), using the specified paint, along the specified path. The - * paint's Align setting determins where along the path to start the text. + * paint's Align setting determines where along the path to start the text. * * @param text The text to be drawn + * @param index The starting index within the text to be drawn + * @param count Starting from index, the number of characters to draw * @param path The path the text should follow for its baseline * @param hOffset The distance along the path to add to the text's starting position * @param vOffset The distance above(-) or below(+) the path to position the text @@ -1943,7 +2112,7 @@ public class Canvas extends BaseCanvas { /** * Draw the text, with origin at (x,y), using the specified paint, along the specified path. The - * paint's Align setting determins where along the path to start the text. + * paint's Align setting determines where along the path to start the text. * * @param text The text to be drawn * @param path The path the text should follow for its baseline @@ -1993,7 +2162,8 @@ public class Canvas extends BaseCanvas { * the text next to it. * <p> * All text outside the range {@code contextStart..contextEnd} is ignored. The text between - * {@code start} and {@code end} will be laid out and drawn. + * {@code start} and {@code end} will be laid out and drawn. The context range is useful for + * contextual shaping, e.g. Kerning, Arabic contextural form. * <p> * The direction of the run is explicitly specified by {@code isRtl}. Thus, this method is * suitable only for runs of a single direction. Alignment of the text is as determined by the @@ -2022,6 +2192,31 @@ public class Canvas extends BaseCanvas { } /** + * Draw a run of text, all in a single direction, with optional context for complex text + * shaping. + * <p> + * See {@link #drawTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)} for + * more details. This method uses a {@link MeasuredText} rather than CharSequence to represent + * the string. + * + * @param text the text to render + * @param start the start of the text to render. Data before this position can be used for + * shaping context. + * @param end the end of the text to render. Data at or after this position can be used for + * shaping context. + * @param contextStart the index of the start of the shaping context + * @param contextEnd the index of the end of the shaping context + * @param x the x position at which to draw the text + * @param y the y position at which to draw the text + * @param isRtl whether the run is in RTL direction + * @param paint the paint + */ + public void drawTextRun(@NonNull MeasuredText text, int start, int end, int contextStart, + int contextEnd, float x, float y, boolean isRtl, @NonNull Paint paint) { + super.drawTextRun(text, start, end, contextStart, contextEnd, x, y, isRtl, paint); + } + + /** * Draw the array of vertices, interpreted as triangles (based on mode). The verts array is * required, and specifies the x,y pairs for each vertex. If texs is non-null, then it is used * to specify the coordinate in shader coordinates to use at each vertex (the paint must have a @@ -2056,4 +2251,17 @@ public class Canvas extends BaseCanvas { super.drawVertices(mode, vertexCount, verts, vertOffset, texs, texOffset, colors, colorOffset, indices, indexOffset, indexCount, paint); } + + /** + * Draws the given RenderNode. This is only supported in hardware rendering, which can be + * verified by asserting that {@link #isHardwareAccelerated()} is true. If + * {@link #isHardwareAccelerated()} is false then this throws an exception. + * + * See {@link RenderNode} for more information on what a RenderNode is and how to use it. + * + * @param renderNode The RenderNode to draw, must be non-null. + */ + public void drawRenderNode(@NonNull RenderNode renderNode) { + throw new IllegalArgumentException("Software rendering doesn't support drawRenderNode"); + } } diff --git a/graphics/java/android/graphics/CanvasProperty.java b/graphics/java/android/graphics/CanvasProperty.java index ea3886cde274..1275e0827580 100644 --- a/graphics/java/android/graphics/CanvasProperty.java +++ b/graphics/java/android/graphics/CanvasProperty.java @@ -16,6 +16,7 @@ package android.graphics; +import android.annotation.UnsupportedAppUsage; import com.android.internal.util.VirtualRefBasePtr; /** @@ -26,10 +27,12 @@ public final class CanvasProperty<T> { private VirtualRefBasePtr mProperty; + @UnsupportedAppUsage public static CanvasProperty<Float> createFloat(float initialValue) { return new CanvasProperty<Float>(nCreateFloat(initialValue)); } + @UnsupportedAppUsage public static CanvasProperty<Paint> createPaint(Paint initialValue) { return new CanvasProperty<Paint>(nCreatePaint(initialValue.getNativeInstance())); } diff --git a/graphics/java/android/graphics/ColorFilter.java b/graphics/java/android/graphics/ColorFilter.java index b24b9885d1b0..4c2ef84404e2 100644 --- a/graphics/java/android/graphics/ColorFilter.java +++ b/graphics/java/android/graphics/ColorFilter.java @@ -26,8 +26,9 @@ import libcore.util.NativeAllocationRegistry; public class ColorFilter { private static class NoImagePreloadHolder { - public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( - ColorFilter.class.getClassLoader(), nativeGetFinalizer(), 50); + public static final NativeAllocationRegistry sRegistry = + NativeAllocationRegistry.createMalloced( + ColorFilter.class.getClassLoader(), nativeGetFinalizer()); } /** diff --git a/graphics/java/android/graphics/ColorMatrixColorFilter.java b/graphics/java/android/graphics/ColorMatrixColorFilter.java index 9201a2e2310e..0f7980cc32e4 100644 --- a/graphics/java/android/graphics/ColorMatrixColorFilter.java +++ b/graphics/java/android/graphics/ColorMatrixColorFilter.java @@ -18,6 +18,7 @@ package android.graphics; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UnsupportedAppUsage; /** * A color filter that transforms colors through a 4x5 color matrix. This filter @@ -26,6 +27,7 @@ import android.annotation.Nullable; * @see ColorMatrix */ public class ColorMatrixColorFilter extends ColorFilter { + @UnsupportedAppUsage private final ColorMatrix mMatrix = new ColorMatrix(); /** @@ -76,6 +78,7 @@ public class ColorMatrixColorFilter extends ColorFilter { * * @hide */ + @UnsupportedAppUsage public void setColorMatrix(@Nullable ColorMatrix matrix) { discardNativeInstance(); if (matrix == null) { @@ -104,6 +107,7 @@ public class ColorMatrixColorFilter extends ColorFilter { * * @hide */ + @UnsupportedAppUsage public void setColorMatrixArray(@Nullable float[] array) { // called '...Array' so that passing null isn't ambiguous discardNativeInstance(); diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java index 5814df5b5cc0..9c4b5e8b0165 100644 --- a/graphics/java/android/graphics/ColorSpace.java +++ b/graphics/java/android/graphics/ColorSpace.java @@ -25,6 +25,8 @@ import android.annotation.Size; import android.annotation.SuppressAutoDoc; import android.util.Pair; +import libcore.util.NativeAllocationRegistry; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -199,6 +201,9 @@ public abstract class ColorSpace { private static final float[] NTSC_1953_PRIMARIES = { 0.67f, 0.33f, 0.21f, 0.71f, 0.14f, 0.08f }; private static final float[] ILLUMINANT_D50_XYZ = { 0.964212f, 1.0f, 0.825188f }; + private static final Rgb.TransferParameters SRGB_TRANSFER_PARAMETERS = + new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4); + // See static initialization block next to #get(Named) private static final ColorSpace[] sNamedColorSpaces = new ColorSpace[Named.values().length]; @@ -239,7 +244,7 @@ public abstract class ColorSpace { * <tr> * <td>Opto-electronic transfer function (OETF)</td> * <td colspan="4">\(\begin{equation} - * C_{sRGB} = \begin{cases} 12.92 \times C_{linear} & C_{linear} \lt 0.0031308 \\ + * C_{sRGB} = \begin{cases} 12.92 \times C_{linear} & C_{linear} \lt 0.0031308 \\\ * 1.055 \times C_{linear}^{\frac{1}{2.4}} - 0.055 & C_{linear} \ge 0.0031308 \end{cases} * \end{equation}\) * </td> @@ -247,7 +252,7 @@ public abstract class ColorSpace { * <tr> * <td>Electro-optical transfer function (EOTF)</td> * <td colspan="4">\(\begin{equation} - * C_{linear} = \begin{cases}\frac{C_{sRGB}}{12.92} & C_{sRGB} \lt 0.04045 \\ + * C_{linear} = \begin{cases}\frac{C_{sRGB}}{12.92} & C_{sRGB} \lt 0.04045 \\\ * \left( \frac{C_{sRGB} + 0.055}{1.055} \right) ^{2.4} & C_{sRGB} \ge 0.04045 \end{cases} * \end{equation}\) * </td> @@ -302,7 +307,7 @@ public abstract class ColorSpace { * <td>Opto-electronic transfer function (OETF)</td> * <td colspan="4">\(\begin{equation} * C_{scRGB} = \begin{cases} sign(C_{linear}) 12.92 \times \left| C_{linear} \right| & - * \left| C_{linear} \right| \lt 0.0031308 \\ + * \left| C_{linear} \right| \lt 0.0031308 \\\ * sign(C_{linear}) 1.055 \times \left| C_{linear} \right| ^{\frac{1}{2.4}} - 0.055 & * \left| C_{linear} \right| \ge 0.0031308 \end{cases} * \end{equation}\) @@ -312,7 +317,7 @@ public abstract class ColorSpace { * <td>Electro-optical transfer function (EOTF)</td> * <td colspan="4">\(\begin{equation} * C_{linear} = \begin{cases}sign(C_{scRGB}) \frac{\left| C_{scRGB} \right|}{12.92} & - * \left| C_{scRGB} \right| \lt 0.04045 \\ + * \left| C_{scRGB} \right| \lt 0.04045 \\\ * sign(C_{scRGB}) \left( \frac{\left| C_{scRGB} \right| + 0.055}{1.055} \right) ^{2.4} & * \left| C_{scRGB} \right| \ge 0.04045 \end{cases} * \end{equation}\) @@ -367,7 +372,7 @@ public abstract class ColorSpace { * <tr> * <td>Opto-electronic transfer function (OETF)</td> * <td colspan="4">\(\begin{equation} - * C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\ + * C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\\ * 1.099 \times C_{linear}^{\frac{1}{2.2}} - 0.099 & C_{linear} \ge 0.018 \end{cases} * \end{equation}\) * </td> @@ -375,7 +380,7 @@ public abstract class ColorSpace { * <tr> * <td>Electro-optical transfer function (EOTF)</td> * <td colspan="4">\(\begin{equation} - * C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\ + * C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\\ * \left( \frac{C_{BT709} + 0.099}{1.099} \right) ^{2.2} & C_{BT709} \ge 0.081 \end{cases} * \end{equation}\) * </td> @@ -402,7 +407,7 @@ public abstract class ColorSpace { * <tr> * <td>Opto-electronic transfer function (OETF)</td> * <td colspan="4">\(\begin{equation} - * C_{BT2020} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.0181 \\ + * C_{BT2020} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.0181 \\\ * 1.0993 \times C_{linear}^{\frac{1}{2.2}} - 0.0993 & C_{linear} \ge 0.0181 \end{cases} * \end{equation}\) * </td> @@ -410,7 +415,7 @@ public abstract class ColorSpace { * <tr> * <td>Electro-optical transfer function (EOTF)</td> * <td colspan="4">\(\begin{equation} - * C_{linear} = \begin{cases}\frac{C_{BT2020}}{4.5} & C_{BT2020} \lt 0.08145 \\ + * C_{linear} = \begin{cases}\frac{C_{BT2020}}{4.5} & C_{BT2020} \lt 0.08145 \\\ * \left( \frac{C_{BT2020} + 0.0993}{1.0993} \right) ^{2.2} & C_{BT2020} \ge 0.08145 \end{cases} * \end{equation}\) * </td> @@ -464,7 +469,7 @@ public abstract class ColorSpace { * <tr> * <td>Opto-electronic transfer function (OETF)</td> * <td colspan="4">\(\begin{equation} - * C_{DisplayP3} = \begin{cases} 12.92 \times C_{linear} & C_{linear} \lt 0.0030186 \\ + * C_{DisplayP3} = \begin{cases} 12.92 \times C_{linear} & C_{linear} \lt 0.0030186 \\\ * 1.055 \times C_{linear}^{\frac{1}{2.4}} - 0.055 & C_{linear} \ge 0.0030186 \end{cases} * \end{equation}\) * </td> @@ -472,8 +477,8 @@ public abstract class ColorSpace { * <tr> * <td>Electro-optical transfer function (EOTF)</td> * <td colspan="4">\(\begin{equation} - * C_{linear} = \begin{cases}\frac{C_{DisplayP3}}{12.92} & C_{sRGB} \lt 0.039 \\ - * \left( \frac{C_{DisplayP3} + 0.055}{1.055} \right) ^{2.4} & C_{sRGB} \ge 0.039 \end{cases} + * C_{linear} = \begin{cases}\frac{C_{DisplayP3}}{12.92} & C_{sRGB} \lt 0.04045 \\\ + * \left( \frac{C_{DisplayP3} + 0.055}{1.055} \right) ^{2.4} & C_{sRGB} \ge 0.04045 \end{cases} * \end{equation}\) * </td> * </tr> @@ -499,7 +504,7 @@ public abstract class ColorSpace { * <tr> * <td>Opto-electronic transfer function (OETF)</td> * <td colspan="4">\(\begin{equation} - * C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\ + * C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\\ * 1.099 \times C_{linear}^{\frac{1}{2.2}} - 0.099 & C_{linear} \ge 0.018 \end{cases} * \end{equation}\) * </td> @@ -507,7 +512,7 @@ public abstract class ColorSpace { * <tr> * <td>Electro-optical transfer function (EOTF)</td> * <td colspan="4">\(\begin{equation} - * C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\ + * C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\\ * \left( \frac{C_{BT709} + 0.099}{1.099} \right) ^{2.2} & C_{BT709} \ge 0.081 \end{cases} * \end{equation}\) * </td> @@ -534,7 +539,7 @@ public abstract class ColorSpace { * <tr> * <td>Opto-electronic transfer function (OETF)</td> * <td colspan="4">\(\begin{equation} - * C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\ + * C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\\ * 1.099 \times C_{linear}^{\frac{1}{2.2}} - 0.099 & C_{linear} \ge 0.018 \end{cases} * \end{equation}\) * </td> @@ -542,7 +547,7 @@ public abstract class ColorSpace { * <tr> * <td>Electro-optical transfer function (EOTF)</td> * <td colspan="4">\(\begin{equation} - * C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\ + * C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\\ * \left( \frac{C_{BT709} + 0.099}{1.099} \right) ^{2.2} & C_{BT709} \ge 0.081 \end{cases} * \end{equation}\) * </td> @@ -596,7 +601,7 @@ public abstract class ColorSpace { * <tr> * <td>Opto-electronic transfer function (OETF)</td> * <td colspan="4">\(\begin{equation} - * C_{ROMM} = \begin{cases} 16 \times C_{linear} & C_{linear} \lt 0.001953 \\ + * C_{ROMM} = \begin{cases} 16 \times C_{linear} & C_{linear} \lt 0.001953 \\\ * C_{linear}^{\frac{1}{1.8}} & C_{linear} \ge 0.001953 \end{cases} * \end{equation}\) * </td> @@ -604,7 +609,7 @@ public abstract class ColorSpace { * <tr> * <td>Electro-optical transfer function (EOTF)</td> * <td colspan="4">\(\begin{equation} - * C_{linear} = \begin{cases}\frac{C_{ROMM}}{16} & C_{ROMM} \lt 0.031248 \\ + * C_{linear} = \begin{cases}\frac{C_{ROMM}}{16} & C_{ROMM} \lt 0.031248 \\\ * C_{ROMM}^{1.8} & C_{ROMM} \ge 0.031248 \end{cases} * \end{equation}\) * </td> @@ -759,12 +764,12 @@ public abstract class ColorSpace { * * $$\begin{align*} * \left[ \begin{array}{c} L_1\\ M_1\\ S_1 \end{array} \right] &= - * A \left[ \begin{array}{c} W1_X\\ W1_Y\\ W1_Z \end{array} \right] \\ + * A \left[ \begin{array}{c} W1_X\\ W1_Y\\ W1_Z \end{array} \right] \\\ * \left[ \begin{array}{c} L_2\\ M_2\\ S_2 \end{array} \right] &= - * A \left[ \begin{array}{c} W2_X\\ W2_Y\\ W2_Z \end{array} \right] \\ - * D &= \left[ \begin{matrix} \frac{L_2}{L_1} & 0 & 0 \\ - * 0 & \frac{M_2}{M_1} & 0 \\ - * 0 & 0 & \frac{S_2}{S_1} \end{matrix} \right] \\ + * A \left[ \begin{array}{c} W2_X\\ W2_Y\\ W2_Z \end{array} \right] \\\ + * D &= \left[ \begin{matrix} \frac{L_2}{L_1} & 0 & 0 \\\ + * 0 & \frac{M_2}{M_1} & 0 \\\ + * 0 & 0 & \frac{S_2}{S_1} \end{matrix} \right] \\\ * T &= A^{-1}.D.A * \end{align*}$$ * @@ -984,11 +989,12 @@ public abstract class ColorSpace { * {@link Named#SRGB sRGB} primaries. * </li> * <li> - * Its white point is withing 1e-3 of the CIE standard + * Its white point is within 1e-3 of the CIE standard * illuminant {@link #ILLUMINANT_D65 D65}. * </li> * <li>Its opto-electronic transfer function is not linear.</li> * <li>Its electro-optical transfer function is not linear.</li> + * <li>Its transfer functions yield values within 1e-3 of {@link Named#SRGB}.</li> * <li>Its range is \([0..1]\).</li> * </ul> * <p>This method always returns true for {@link Named#SRGB}.</p> @@ -1340,6 +1346,26 @@ public abstract class ColorSpace { } /** + * Helper method for creating native SkColorSpace. + * + * This essentially calls adapt on a ColorSpace that has not been fully + * created. It also does not fully create the adapted ColorSpace, but + * just returns the transform. + */ + @NonNull @Size(9) + private static float[] adaptToIlluminantD50( + @NonNull @Size(2) float[] origWhitePoint, + @NonNull @Size(9) float[] origTransform) { + float[] desired = ILLUMINANT_D50; + if (compare(origWhitePoint, desired)) return origTransform; + + float[] xyz = xyYToXyz(desired); + float[] adaptationTransform = chromaticAdaptation(Adaptation.BRADFORD.mTransform, + xyYToXyz(origWhitePoint), xyz); + return mul3x3(adaptationTransform, origTransform); + } + + /** * <p>Returns an instance of {@link ColorSpace} whose ID matches the * specified ID.</p> * @@ -1354,9 +1380,9 @@ public abstract class ColorSpace { */ @NonNull static ColorSpace get(@IntRange(from = MIN_ID, to = MAX_ID) int index) { - if (index < 0 || index > Named.values().length) { + if (index < 0 || index >= Named.values().length) { throw new IllegalArgumentException("Invalid ID, must be in the range [0.." + - Named.values().length + "]"); + Named.values().length + ")"); } return sNamedColorSpaces[index]; } @@ -1430,7 +1456,7 @@ public abstract class ColorSpace { "sRGB IEC61966-2.1", SRGB_PRIMARIES, ILLUMINANT_D65, - new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4), + SRGB_TRANSFER_PARAMETERS, Named.SRGB.ordinal() ); sNamedColorSpaces[Named.LINEAR_SRGB.ordinal()] = new ColorSpace.Rgb( @@ -1445,9 +1471,11 @@ public abstract class ColorSpace { "scRGB-nl IEC 61966-2-2:2003", SRGB_PRIMARIES, ILLUMINANT_D65, + null, x -> absRcpResponse(x, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4), x -> absResponse(x, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4), -0.799f, 2.399f, + SRGB_TRANSFER_PARAMETERS, Named.EXTENDED_SRGB.ordinal() ); sNamedColorSpaces[Named.LINEAR_EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb( @@ -1484,7 +1512,7 @@ public abstract class ColorSpace { "Display P3", new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f }, ILLUMINANT_D65, - new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.039, 2.4), + SRGB_TRANSFER_PARAMETERS, Named.DISPLAY_P3.ordinal() ); sNamedColorSpaces[Named.NTSC_1953.ordinal()] = new ColorSpace.Rgb( @@ -1660,10 +1688,12 @@ public abstract class ColorSpace { * @param rhs 3x3 matrix, as a non-null array of 9 floats * @return A new array of 9 floats containing the result of the multiplication * of rhs by lhs + * + * @hide */ @NonNull @Size(9) - private static float[] mul3x3(@NonNull @Size(9) float[] lhs, @NonNull @Size(9) float[] rhs) { + public static float[] mul3x3(@NonNull @Size(9) float[] lhs, @NonNull @Size(9) float[] rhs) { float[] r = new float[9]; r[0] = lhs[0] * rhs[0] + lhs[3] * rhs[1] + lhs[6] * rhs[2]; r[1] = lhs[1] * rhs[0] + lhs[4] * rhs[1] + lhs[7] * rhs[2]; @@ -1779,6 +1809,113 @@ public abstract class ColorSpace { } /** + * <p>Computes the chromaticity coordinates of a specified correlated color + * temperature (CCT) on the Planckian locus. The specified CCT must be + * greater than 0. A meaningful CCT range is [1667, 25000].</p> + * + * <p>The transform is computed using the methods in Kang et + * al., <i>Design of Advanced Color - Temperature Control System for HDTV + * Applications</i>, Journal of Korean Physical Society 41, 865-871 + * (2002).</p> + * + * @param cct The correlated color temperature, in Kelvin + * @return Corresponding XYZ values + * @throws IllegalArgumentException If cct is invalid + * + * @hide + */ + @NonNull + @Size(3) + public static float[] cctToXyz(@IntRange(from = 1) int cct) { + if (cct < 1) { + throw new IllegalArgumentException("Temperature must be greater than 0"); + } + + final float icct = 1e3f / cct; + final float icct2 = icct * icct; + final float x = cct <= 4000.0f ? + 0.179910f + 0.8776956f * icct - 0.2343589f * icct2 - 0.2661239f * icct2 * icct : + 0.240390f + 0.2226347f * icct + 2.1070379f * icct2 - 3.0258469f * icct2 * icct; + + final float x2 = x * x; + final float y = cct <= 2222.0f ? + -0.20219683f + 2.18555832f * x - 1.34811020f * x2 - 1.1063814f * x2 * x : + cct <= 4000.0f ? + -0.16748867f + 2.09137015f * x - 1.37418593f * x2 - 0.9549476f * x2 * x : + -0.37001483f + 3.75112997f * x - 5.8733867f * x2 + 3.0817580f * x2 * x; + + return xyYToXyz(new float[] {x, y}); + } + + /** + * <p>Computes the chromaticity coordinates of a CIE series D illuminant + * from the specified correlated color temperature (CCT). The specified CCT + * must be greater than 0. A meaningful CCT range is [4000, 25000].</p> + * + * <p>The transform is computed using the methods referred to in Kang et + * al., <i>Design of Advanced Color - Temperature Control System for HDTV + * Applications</i>, Journal of Korean Physical Society 41, 865-871 + * (2002).</p> + * + * @param cct The correlated color temperature, in Kelvin + * @return Corresponding XYZ values + * @throws IllegalArgumentException If cct is invalid + * + * @hide + */ + @NonNull + @Size(3) + public static float[] cctToIlluminantdXyz(@IntRange(from = 1) int cct) { + if (cct < 1) { + throw new IllegalArgumentException("Temperature must be greater than 0"); + } + + final float icct = 1.0f / cct; + final float icct2 = icct * icct; + final float x = cct <= 7000.0f ? + 0.244063f + 0.09911e3f * icct + 2.9678e6f * icct2 - 4.6070e9f * icct2 * icct : + 0.237040f + 0.24748e3f * icct + 1.9018e6f * icct2 - 2.0064e9f * icct2 * icct; + final float y = -3.0f * x * x + 2.87f * x - 0.275f; + return xyYToXyz(new float[] {x, y}); + } + + /** + * <p>Computes the chromatic adaptation transform from the specified + * source white point to the specified destination white point.</p> + * + * <p>The transform is computed using the von Kries method, described + * in more details in the documentation of {@link Adaptation}. The + * {@link Adaptation} enum provides different matrices that can be + * used to perform the adaptation.</p> + * + * @param adaptation The adaptation method + * @param srcWhitePoint The white point to adapt from + * @param dstWhitePoint The white point to adapt to + * @return A 3x3 matrix as a non-null array of 9 floats + * + * @hide + */ + @NonNull + @Size(9) + public static float[] chromaticAdaptation(@NonNull Adaptation adaptation, + @NonNull @Size(min = 2, max = 3) float[] srcWhitePoint, + @NonNull @Size(min = 2, max = 3) float[] dstWhitePoint) { + float[] srcXyz = srcWhitePoint.length == 3 ? + Arrays.copyOf(srcWhitePoint, 3) : xyYToXyz(srcWhitePoint); + float[] dstXyz = dstWhitePoint.length == 3 ? + Arrays.copyOf(dstWhitePoint, 3) : xyYToXyz(dstWhitePoint); + + if (compare(srcXyz, dstXyz)) { + return new float[] { + 1.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 1.0f + }; + } + return chromaticAdaptation(adaptation.mTransform, srcXyz, dstXyz); + } + + /** * Implementation of the CIE XYZ color space. Assumes the white point is D50. */ @AnyThread @@ -1900,6 +2037,15 @@ public abstract class ColorSpace { } /** + * Retrieve the native SkColorSpace object for passing to native. + * + * Only valid on ColorSpace.Rgb. + */ + long getNativeInstance() { + throw new IllegalArgumentException("colorSpace must be an RGB color space"); + } + + /** * {@usesMathJax} * * <p>An RGB color space is an additive color space using the @@ -2028,7 +2174,7 @@ public abstract class ColorSpace { * <p>The EOTF is of the form:</p> * * \(\begin{equation} - * Y = \begin{cases}c X + f & X \lt d \\ + * Y = \begin{cases}c X + f & X \lt d \\\ * \left( a X + b \right) ^{g} + e & X \ge d \end{cases} * \end{equation}\) * @@ -2066,7 +2212,7 @@ public abstract class ColorSpace { * <p>The EOTF is of the form:</p> * * \(\begin{equation} - * Y = \begin{cases}c X & X \lt d \\ + * Y = \begin{cases}c X & X \lt d \\\ * \left( a X + b \right) ^{g} & X \ge d \end{cases} * \end{equation}\) * @@ -2202,7 +2348,22 @@ public abstract class ColorSpace { private final boolean mIsWideGamut; private final boolean mIsSrgb; - @Nullable private TransferParameters mTransferParameters; + @Nullable private final TransferParameters mTransferParameters; + private final long mNativePtr; + + @Override + long getNativeInstance() { + if (mNativePtr == 0) { + // If this object has TransferParameters, it must have a native object. + throw new IllegalArgumentException("ColorSpace must use an ICC " + + "parametric transfer function! used " + this); + } + return mNativePtr; + } + + private static native long nativeGetNativeFinalizer(); + private static native long nativeCreate(float a, float b, float c, float d, + float e, float f, float g, float[] xyz); /** * <p>Creates a new RGB color space using a 3x3 column-major transform matrix. @@ -2231,8 +2392,8 @@ public abstract class ColorSpace { @NonNull @Size(9) float[] toXYZ, @NonNull DoubleUnaryOperator oetf, @NonNull DoubleUnaryOperator eotf) { - this(name, computePrimaries(toXYZ), computeWhitePoint(toXYZ), - oetf, eotf, 0.0f, 1.0f, MIN_ID); + this(name, computePrimaries(toXYZ), computeWhitePoint(toXYZ), null, + oetf, eotf, 0.0f, 1.0f, null, MIN_ID); } /** @@ -2282,7 +2443,7 @@ public abstract class ColorSpace { @NonNull DoubleUnaryOperator eotf, float min, float max) { - this(name, primaries, whitePoint, oetf, eotf, min, max, MIN_ID); + this(name, primaries, whitePoint, null, oetf, eotf, min, max, null, MIN_ID); } /** @@ -2392,7 +2553,7 @@ public abstract class ColorSpace { @NonNull @Size(min = 2, max = 3) float[] whitePoint, @NonNull TransferParameters function, @IntRange(from = MIN_ID, to = MAX_ID) int id) { - this(name, primaries, whitePoint, + this(name, primaries, whitePoint, null, function.e == 0.0 && function.f == 0.0 ? x -> rcpResponse(x, function.a, function.b, function.c, function.d, function.g) : @@ -2403,8 +2564,7 @@ public abstract class ColorSpace { function.c, function.d, function.g) : x -> response(x, function.a, function.b, function.c, function.d, function.e, function.f, function.g), - 0.0f, 1.0f, id); - mTransferParameters = function; + 0.0f, 1.0f, function, id); } /** @@ -2519,15 +2679,12 @@ public abstract class ColorSpace { float min, float max, @IntRange(from = MIN_ID, to = MAX_ID) int id) { - this(name, primaries, whitePoint, + this(name, primaries, whitePoint, null, gamma == 1.0 ? DoubleUnaryOperator.identity() : x -> Math.pow(x < 0.0 ? 0.0 : x, 1 / gamma), gamma == 1.0 ? DoubleUnaryOperator.identity() : x -> Math.pow(x < 0.0 ? 0.0 : x, gamma), - min, max, id); - mTransferParameters = gamma == 1.0 ? - new TransferParameters(0.0, 0.0, 1.0, 1.0 + Math.ulp(1.0f), gamma) : - new TransferParameters(1.0, 0.0, 0.0, 0.0, gamma); + min, max, new TransferParameters(1.0, 0.0, 0.0, 0.0, gamma), id); } /** @@ -2550,10 +2707,13 @@ public abstract class ColorSpace { * @param name Name of the color space, cannot be null, its length must be >= 1 * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats + * @param transform Computed transform matrix that converts from RGB to XYZ, or + * {@code null} to compute it from {@code primaries} and {@code whitePoint}. * @param oetf Opto-electronic transfer function, cannot be null * @param eotf Electro-optical transfer function, cannot be null * @param min The minimum valid value in this color space's RGB range * @param max The maximum valid value in this color space's RGB range + * @param transferParameters Parameters for the transfer functions * @param id ID of this color space as an integer between {@link #MIN_ID} and {@link #MAX_ID} * * @throws IllegalArgumentException If any of the following conditions is met: @@ -2572,10 +2732,12 @@ public abstract class ColorSpace { @NonNull @Size(min = 1) String name, @NonNull @Size(min = 6, max = 9) float[] primaries, @NonNull @Size(min = 2, max = 3) float[] whitePoint, + @Nullable @Size(9) float[] transform, @NonNull DoubleUnaryOperator oetf, @NonNull DoubleUnaryOperator eotf, float min, float max, + @Nullable TransferParameters transferParameters, @IntRange(from = MIN_ID, to = MAX_ID) int id) { super(name, Model.RGB, id); @@ -2603,7 +2765,15 @@ public abstract class ColorSpace { mWhitePoint = xyWhitePoint(whitePoint); mPrimaries = xyPrimaries(primaries); - mTransform = computeXYZMatrix(mPrimaries, mWhitePoint); + if (transform == null) { + mTransform = computeXYZMatrix(mPrimaries, mWhitePoint); + } else { + if (transform.length != 9) { + throw new IllegalArgumentException("Transform must have 9 entries! Has " + + transform.length); + } + mTransform = transform; + } mInverseTransform = inverse3x3(mTransform); mOetf = oetf; @@ -2616,10 +2786,39 @@ public abstract class ColorSpace { mClampedOetf = oetf.andThen(clamp); mClampedEotf = clamp.andThen(eotf); + mTransferParameters = transferParameters; + // A color space is wide-gamut if its area is >90% of NTSC 1953 and // if it entirely contains the Color space definition in xyY mIsWideGamut = isWideGamut(mPrimaries, min, max); mIsSrgb = isSrgb(mPrimaries, mWhitePoint, oetf, eotf, min, max, id); + + if (mTransferParameters != null) { + if (mWhitePoint == null || mTransform == null) { + throw new IllegalStateException( + "ColorSpace (" + this + ") cannot create native object! mWhitePoint: " + + mWhitePoint + " mTransform: " + mTransform); + } + + // This mimics the old code that was in native. + float[] nativeTransform = adaptToIlluminantD50(mWhitePoint, mTransform); + mNativePtr = nativeCreate((float) mTransferParameters.a, + (float) mTransferParameters.b, + (float) mTransferParameters.c, + (float) mTransferParameters.d, + (float) mTransferParameters.e, + (float) mTransferParameters.f, + (float) mTransferParameters.g, + nativeTransform); + NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativePtr); + } else { + mNativePtr = 0; + } + } + + private static class NoImagePreloadHolder { + public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( + ColorSpace.Rgb.class.getClassLoader(), nativeGetNativeFinalizer(), 0); } /** @@ -2630,27 +2829,9 @@ public abstract class ColorSpace { private Rgb(Rgb colorSpace, @NonNull @Size(9) float[] transform, @NonNull @Size(min = 2, max = 3) float[] whitePoint) { - super(colorSpace.getName(), Model.RGB, -1); - - mWhitePoint = xyWhitePoint(whitePoint); - mPrimaries = colorSpace.mPrimaries; - - mTransform = transform; - mInverseTransform = inverse3x3(transform); - - mMin = colorSpace.mMin; - mMax = colorSpace.mMax; - - mOetf = colorSpace.mOetf; - mEotf = colorSpace.mEotf; - - mClampedOetf = colorSpace.mClampedOetf; - mClampedEotf = colorSpace.mClampedEotf; - - mIsWideGamut = colorSpace.mIsWideGamut; - mIsSrgb = colorSpace.mIsSrgb; - - mTransferParameters = colorSpace.mTransferParameters; + this(colorSpace.getName(), colorSpace.mPrimaries, whitePoint, transform, + colorSpace.mOetf, colorSpace.mEotf, colorSpace.mMin, colorSpace.mMax, + colorSpace.mTransferParameters, MIN_ID); } /** @@ -3083,19 +3264,35 @@ public abstract class ColorSpace { float max, @IntRange(from = MIN_ID, to = MAX_ID) int id) { if (id == 0) return true; - if (!compare(primaries, SRGB_PRIMARIES)) { + if (!ColorSpace.compare(primaries, SRGB_PRIMARIES)) { return false; } - if (!compare(whitePoint, ILLUMINANT_D65)) { + if (!ColorSpace.compare(whitePoint, ILLUMINANT_D65)) { return false; } - if (OETF.applyAsDouble(0.5) < 0.5001) return false; - if (EOTF.applyAsDouble(0.5) > 0.5001) return false; + if (min != 0.0f) return false; if (max != 1.0f) return false; + + // We would have already returned true if this was SRGB itself, so + // it is safe to reference it here. + ColorSpace.Rgb srgb = (ColorSpace.Rgb) get(Named.SRGB); + + for (double x = 0.0; x <= 1.0; x += 1 / 255.0) { + if (!compare(x, OETF, srgb.mOetf)) return false; + if (!compare(x, EOTF, srgb.mEotf)) return false; + } + return true; } + private static boolean compare(double point, @NonNull DoubleUnaryOperator a, + @NonNull DoubleUnaryOperator b) { + double rA = a.applyAsDouble(point); + double rB = b.applyAsDouble(point); + return Math.abs(rA - rB) <= 1e-3; + } + /** * Computes whether the specified CIE xyY or XYZ primaries (with Y set to 1) form * a wide color gamut. A color gamut is considered wide if its area is > 90% diff --git a/graphics/java/android/graphics/ComposeShader.java b/graphics/java/android/graphics/ComposeShader.java index 70a5f53ae1e0..64ee6bf1f30c 100644 --- a/graphics/java/android/graphics/ComposeShader.java +++ b/graphics/java/android/graphics/ComposeShader.java @@ -39,7 +39,8 @@ public class ComposeShader extends Shader { * @param shaderB The colors from this shader are seen as the "src" by the mode * @param mode The mode that combines the colors from the two shaders. If mode * is null, then SRC_OVER is assumed. - */ + */ + @Deprecated public ComposeShader(@NonNull Shader shaderA, @NonNull Shader shaderB, @NonNull Xfermode mode) { this(shaderA, shaderB, mode.porterDuffMode); } @@ -52,12 +53,27 @@ public class ComposeShader extends Shader { * @param shaderA The colors from this shader are seen as the "dst" by the mode * @param shaderB The colors from this shader are seen as the "src" by the mode * @param mode The PorterDuff mode that combines the colors from the two shaders. - */ + * + */ public ComposeShader(@NonNull Shader shaderA, @NonNull Shader shaderB, @NonNull PorterDuff.Mode mode) { this(shaderA, shaderB, mode.nativeInt); } + /** + * Create a new compose shader, given shaders A, B, and a combining PorterDuff mode. + * When the mode is applied, it will be given the result from shader A as its + * "dst", and the result from shader B as its "src". + * + * @param shaderA The colors from this shader are seen as the "dst" by the mode + * @param shaderB The colors from this shader are seen as the "src" by the mode + * @param blendMode The blend mode that combines the colors from the two shaders. + */ + public ComposeShader(@NonNull Shader shaderA, @NonNull Shader shaderB, + @NonNull BlendMode blendMode) { + this(shaderA, shaderB, blendMode.getXfermode().porterDuffMode); + } + private ComposeShader(Shader shaderA, Shader shaderB, int nativeMode) { if (shaderA == null || shaderB == null) { throw new IllegalArgumentException("Shader parameters must not be null"); @@ -87,17 +103,6 @@ public class ComposeShader extends Shader { } } - /** - * @hide - */ - @Override - protected Shader copy() { - final ComposeShader copy = new ComposeShader( - mShaderA.copy(), mShaderB.copy(), mPorterDuffMode); - copyLocalMatrix(copy); - return copy; - } - private static native long nativeCreate(long nativeMatrix, long nativeShaderA, long nativeShaderB, int porterDuffMode); } diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java index c69eb32148c0..5af0da85bb39 100644 --- a/graphics/java/android/graphics/FontFamily.java +++ b/graphics/java/android/graphics/FontFamily.java @@ -17,6 +17,7 @@ package android.graphics; import android.annotation.Nullable; +import android.annotation.UnsupportedAppUsage; import android.content.res.AssetManager; import android.graphics.fonts.FontVariationAxis; import android.text.TextUtils; @@ -35,32 +36,48 @@ import java.nio.channels.FileChannel; * A family of typefaces with different styles. * * @hide + * + * @deprecated Use {@link android.graphics.fonts.FontFamily} instead. */ +@Deprecated public class FontFamily { private static String TAG = "FontFamily"; - private static final NativeAllocationRegistry sBuilderRegistry = new NativeAllocationRegistry( - FontFamily.class.getClassLoader(), nGetBuilderReleaseFunc(), 64); + private static final NativeAllocationRegistry sBuilderRegistry = + NativeAllocationRegistry.createMalloced( + FontFamily.class.getClassLoader(), nGetBuilderReleaseFunc()); private @Nullable Runnable mNativeBuilderCleaner; - private static final NativeAllocationRegistry sFamilyRegistry = new NativeAllocationRegistry( - FontFamily.class.getClassLoader(), nGetFamilyReleaseFunc(), 64); + private static final NativeAllocationRegistry sFamilyRegistry = + NativeAllocationRegistry.createMalloced( + FontFamily.class.getClassLoader(), nGetFamilyReleaseFunc()); /** * @hide + * + * This cannot be deleted because it's in use by AndroidX. */ + @UnsupportedAppUsage(trackingBug = 123768928) public long mNativePtr; // Points native font family builder. Must be zero after freezing this family. private long mBuilderPtr; + /** + * This cannot be deleted because it's in use by AndroidX. + */ + @UnsupportedAppUsage(trackingBug = 123768928) public FontFamily() { mBuilderPtr = nInitBuilder(null, 0); mNativeBuilderCleaner = sBuilderRegistry.registerNativeAllocation(this, mBuilderPtr); } + /** + * This cannot be deleted because it's in use by AndroidX. + */ + @UnsupportedAppUsage(trackingBug = 123768928) public FontFamily(@Nullable String[] langs, int variant) { final String langsString; if (langs == null || langs.length == 0) { @@ -79,7 +96,10 @@ public class FontFamily { * * @return boolean returns false if some error happens in native code, e.g. broken font file is * passed, etc. + * + * This cannot be deleted because it's in use by AndroidX. */ + @UnsupportedAppUsage(trackingBug = 123768928) public boolean freeze() { if (mBuilderPtr == 0) { throw new IllegalStateException("This FontFamily is already frozen"); @@ -93,6 +113,10 @@ public class FontFamily { return mNativePtr != 0; } + /** + * This cannot be deleted because it's in use by AndroidX. + */ + @UnsupportedAppUsage(trackingBug = 123768928) public void abortCreation() { if (mBuilderPtr == 0) { throw new IllegalStateException("This FontFamily is already frozen or abandoned"); @@ -101,6 +125,10 @@ public class FontFamily { mBuilderPtr = 0; } + /** + * This cannot be deleted because it's in use by AndroidX. + */ + @UnsupportedAppUsage(trackingBug = 123768928) public boolean addFont(String path, int ttcIndex, FontVariationAxis[] axes, int weight, int italic) { if (mBuilderPtr == 0) { @@ -122,6 +150,10 @@ public class FontFamily { } } + /** + * This cannot be deleted because it's in use by AndroidX. + */ + @UnsupportedAppUsage(trackingBug = 123768928) public boolean addFontFromBuffer(ByteBuffer font, int ttcIndex, FontVariationAxis[] axes, int weight, int italic) { if (mBuilderPtr == 0) { @@ -146,7 +178,10 @@ public class FontFamily { * @param isItalic Whether this font is italic. If the weight is set to 0, this will be resolved * using the OS/2 table in the font. * @return + * + * This cannot be deleted because it's in use by AndroidX. */ + @UnsupportedAppUsage(trackingBug = 123768928) public boolean addFontFromAssetManager(AssetManager mgr, String path, int cookie, boolean isAsset, int ttcIndex, int weight, int isItalic, FontVariationAxis[] axes) { diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java index 431d0e0eb7b4..21cc3757a40e 100644 --- a/graphics/java/android/graphics/FontListParser.java +++ b/graphics/java/android/graphics/FontListParser.java @@ -16,6 +16,7 @@ package android.graphics; +import android.annotation.UnsupportedAppUsage; import android.graphics.fonts.FontVariationAxis; import android.text.FontConfig; import android.util.Xml; @@ -37,18 +38,27 @@ import java.util.regex.Pattern; public class FontListParser { /* Parse fallback list (no names) */ + @UnsupportedAppUsage public static FontConfig parse(InputStream in) throws XmlPullParserException, IOException { + return parse(in, "/system/fonts"); + } + + /** + * Parse the fonts.xml + */ + public static FontConfig parse(InputStream in, String fontDir) + throws XmlPullParserException, IOException { try { XmlPullParser parser = Xml.newPullParser(); parser.setInput(in, null); parser.nextTag(); - return readFamilies(parser); + return readFamilies(parser, fontDir); } finally { in.close(); } } - private static FontConfig readFamilies(XmlPullParser parser) + private static FontConfig readFamilies(XmlPullParser parser, String fontDir) throws XmlPullParserException, IOException { List<FontConfig.Family> families = new ArrayList<>(); List<FontConfig.Alias> aliases = new ArrayList<>(); @@ -58,7 +68,7 @@ public class FontListParser { if (parser.getEventType() != XmlPullParser.START_TAG) continue; String tag = parser.getName(); if (tag.equals("family")) { - families.add(readFamily(parser)); + families.add(readFamily(parser, fontDir)); } else if (tag.equals("alias")) { aliases.add(readAlias(parser)); } else { @@ -69,18 +79,20 @@ public class FontListParser { aliases.toArray(new FontConfig.Alias[aliases.size()])); } - private static FontConfig.Family readFamily(XmlPullParser parser) + /** + * Reads a family element + */ + public static FontConfig.Family readFamily(XmlPullParser parser, String fontDir) throws XmlPullParserException, IOException { final String name = parser.getAttributeValue(null, "name"); - final String lang = parser.getAttributeValue(null, "lang"); - final String[] langs = lang == null ? null : lang.split("\\s+"); + final String lang = parser.getAttributeValue("", "lang"); final String variant = parser.getAttributeValue(null, "variant"); final List<FontConfig.Font> fonts = new ArrayList<FontConfig.Font>(); while (parser.next() != XmlPullParser.END_TAG) { if (parser.getEventType() != XmlPullParser.START_TAG) continue; final String tag = parser.getName(); if (tag.equals("font")) { - fonts.add(readFont(parser)); + fonts.add(readFont(parser, fontDir)); } else { skip(parser); } @@ -93,7 +105,7 @@ public class FontListParser { intVariant = FontConfig.Family.VARIANT_ELEGANT; } } - return new FontConfig.Family(name, fonts.toArray(new FontConfig.Font[fonts.size()]), langs, + return new FontConfig.Family(name, fonts.toArray(new FontConfig.Font[fonts.size()]), lang, intVariant); } @@ -101,7 +113,7 @@ public class FontListParser { private static final Pattern FILENAME_WHITESPACE_PATTERN = Pattern.compile("^[ \\n\\r\\t]+|[ \\n\\r\\t]+$"); - private static FontConfig.Font readFont(XmlPullParser parser) + private static FontConfig.Font readFont(XmlPullParser parser, String fontDir) throws XmlPullParserException, IOException { String indexStr = parser.getAttributeValue(null, "index"); int index = indexStr == null ? 0 : Integer.parseInt(indexStr); @@ -124,8 +136,8 @@ public class FontListParser { } } String sanitizedName = FILENAME_WHITESPACE_PATTERN.matcher(filename).replaceAll(""); - return new FontConfig.Font(sanitizedName, index, - axes.toArray(new FontVariationAxis[axes.size()]), weight, isItalic, fallbackFor); + return new FontConfig.Font(fontDir + sanitizedName, index, axes.toArray( + new FontVariationAxis[axes.size()]), weight, isItalic, fallbackFor); } private static FontVariationAxis readAxis(XmlPullParser parser) @@ -136,7 +148,10 @@ public class FontListParser { return new FontVariationAxis(tagStr, Float.parseFloat(styleValueStr)); } - private static FontConfig.Alias readAlias(XmlPullParser parser) + /** + * Reads alias elements + */ + public static FontConfig.Alias readAlias(XmlPullParser parser) throws XmlPullParserException, IOException { String name = parser.getAttributeValue(null, "name"); String toName = parser.getAttributeValue(null, "to"); @@ -151,7 +166,10 @@ public class FontListParser { return new FontConfig.Alias(name, toName, weight); } - private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException { + /** + * Skip until next element + */ + public static void skip(XmlPullParser parser) throws XmlPullParserException, IOException { int depth = 1; while (depth > 0) { switch (parser.next()) { diff --git a/graphics/java/android/graphics/FrameInfo.java b/graphics/java/android/graphics/FrameInfo.java new file mode 100644 index 000000000000..42a5cc43eaa0 --- /dev/null +++ b/graphics/java/android/graphics/FrameInfo.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2015 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.graphics; + +import android.annotation.LongDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Class that contains all the timing information for the current frame. This + * is used in conjunction with the hardware renderer to provide + * continous-monitoring jank events + * + * All times in nanoseconds from CLOCK_MONOTONIC/System.nanoTime() + * + * To minimize overhead from System.nanoTime() calls we infer durations of + * things by knowing the ordering of the events. For example, to know how + * long layout & measure took it's displayListRecordStart - performTraversalsStart. + * + * These constants must be kept in sync with FrameInfo.h in libhwui and are + * used for indexing into AttachInfo's frameInfo long[], which is intended + * to be quick to pass down to native via JNI, hence a pre-packed format + * + * @hide + */ +public final class FrameInfo { + + public long[] frameInfo = new long[9]; + + // Various flags set to provide extra metadata about the current frame + private static final int FLAGS = 0; + + // Is this the first-draw following a window layout? + public static final long FLAG_WINDOW_LAYOUT_CHANGED = 1; + + // A renderer associated with just a Surface, not with a ViewRootImpl instance. + public static final long FLAG_SURFACE_CANVAS = 1 << 2; + + @LongDef(flag = true, value = { + FLAG_WINDOW_LAYOUT_CHANGED, FLAG_SURFACE_CANVAS }) + @Retention(RetentionPolicy.SOURCE) + public @interface FrameInfoFlags {} + + // The intended vsync time, unadjusted by jitter + private static final int INTENDED_VSYNC = 1; + + // Jitter-adjusted vsync time, this is what was used as input into the + // animation & drawing system + private static final int VSYNC = 2; + + // The time of the oldest input event + private static final int OLDEST_INPUT_EVENT = 3; + + // The time of the newest input event + private static final int NEWEST_INPUT_EVENT = 4; + + // When input event handling started + private static final int HANDLE_INPUT_START = 5; + + // When animation evaluations started + private static final int ANIMATION_START = 6; + + // When ViewRootImpl#performTraversals() started + private static final int PERFORM_TRAVERSALS_START = 7; + + // When View:draw() started + private static final int DRAW_START = 8; + + /** checkstyle */ + public void setVsync(long intendedVsync, long usedVsync) { + frameInfo[INTENDED_VSYNC] = intendedVsync; + frameInfo[VSYNC] = usedVsync; + frameInfo[OLDEST_INPUT_EVENT] = Long.MAX_VALUE; + frameInfo[NEWEST_INPUT_EVENT] = 0; + frameInfo[FLAGS] = 0; + } + + /** checkstyle */ + public void updateInputEventTime(long inputEventTime, long inputEventOldestTime) { + if (inputEventOldestTime < frameInfo[OLDEST_INPUT_EVENT]) { + frameInfo[OLDEST_INPUT_EVENT] = inputEventOldestTime; + } + if (inputEventTime > frameInfo[NEWEST_INPUT_EVENT]) { + frameInfo[NEWEST_INPUT_EVENT] = inputEventTime; + } + } + + /** checkstyle */ + public void markInputHandlingStart() { + frameInfo[HANDLE_INPUT_START] = System.nanoTime(); + } + + /** checkstyle */ + public void markAnimationsStart() { + frameInfo[ANIMATION_START] = System.nanoTime(); + } + + /** checkstyle */ + public void markPerformTraversalsStart() { + frameInfo[PERFORM_TRAVERSALS_START] = System.nanoTime(); + } + + /** checkstyle */ + public void markDrawStart() { + frameInfo[DRAW_START] = System.nanoTime(); + } + + /** checkstyle */ + public void addFlags(@FrameInfoFlags long flags) { + frameInfo[FLAGS] |= flags; + } + +} diff --git a/graphics/java/android/graphics/GraphicBuffer.java b/graphics/java/android/graphics/GraphicBuffer.java index 61dd37fb0581..3b1fc70397ea 100644 --- a/graphics/java/android/graphics/GraphicBuffer.java +++ b/graphics/java/android/graphics/GraphicBuffer.java @@ -16,6 +16,7 @@ package android.graphics; +import android.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; @@ -52,8 +53,8 @@ public class GraphicBuffer implements Parcelable { private final int mHeight; private final int mFormat; private final int mUsage; - private final boolean mCapturedSecureLayers; // Note: do not rename, this field is used by native code + @UnsupportedAppUsage private final long mNativeObject; // These two fields are only used by lock/unlockCanvas() @@ -83,58 +84,32 @@ public class GraphicBuffer implements Parcelable { } /** - * Private use only. See {@link #create(int, int, int, int, boolean)}. + * Private use only. See {@link #create(int, int, int, int)}. */ - private GraphicBuffer(int width, int height, int format, int usage, long nativeObject, - boolean capturedSecureLayers) { + @UnsupportedAppUsage + private GraphicBuffer(int width, int height, int format, int usage, long nativeObject) { mWidth = width; mHeight = height; mFormat = format; mUsage = usage; mNativeObject = nativeObject; - mCapturedSecureLayers = capturedSecureLayers; - } - - /** - * Private use only. See {@link #create(int, int, int, int)}. - */ - private GraphicBuffer(int width, int height, int format, int usage, long nativeObject) { - this(width, height, format, usage, nativeObject, false); } /** * For SurfaceControl JNI. * @hide */ + @UnsupportedAppUsage public static GraphicBuffer createFromExisting(int width, int height, - int format, int usage, long unwrappedNativeObject, - boolean capturedSecureLayers) { + int format, int usage, long unwrappedNativeObject) { long nativeObject = nWrapGraphicBuffer(unwrappedNativeObject); if (nativeObject != 0) { - return new GraphicBuffer(width, height, format, usage, nativeObject, - capturedSecureLayers); + return new GraphicBuffer(width, height, format, usage, nativeObject); } return null; } /** - * For SurfaceControl JNI. Provides and ignored value for capturedSecureLayers for backwards - * compatibility - * @hide - */ - public static GraphicBuffer createFromExisting(int width, int height, - int format, int usage, long unwrappedNativeObject) { - return createFromExisting(width, height, format, usage, unwrappedNativeObject, false); - } - - /** - * Returns true if the buffer contains visible secure layers. - */ - public boolean doesContainSecureLayers() { - return mCapturedSecureLayers; - } - - /** * Returns the width of this buffer in pixels. */ public int getWidth() { @@ -303,7 +278,8 @@ public class GraphicBuffer implements Parcelable { nWriteGraphicBufferToParcel(mNativeObject, dest); } - public static final Parcelable.Creator<GraphicBuffer> CREATOR = + @UnsupportedAppUsage + public static final @android.annotation.NonNull Parcelable.Creator<GraphicBuffer> CREATOR = new Parcelable.Creator<GraphicBuffer>() { public GraphicBuffer createFromParcel(Parcel in) { int width = in.readInt(); diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java new file mode 100644 index 000000000000..b6b2d4e1c46a --- /dev/null +++ b/graphics/java/android/graphics/HardwareRenderer.java @@ -0,0 +1,1183 @@ +/* + * Copyright (C) 2018 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.graphics; + +import android.annotation.FloatRange; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.Activity; +import android.app.ActivityManager; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; +import android.util.TimeUtils; +import android.view.FrameMetricsObserver; +import android.view.IGraphicsStats; +import android.view.IGraphicsStatsCallback; +import android.view.NativeVectorDrawableAnimator; +import android.view.PixelCopy; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.TextureLayer; +import android.view.animation.AnimationUtils; + +import com.android.internal.util.VirtualRefBasePtr; + +import java.io.File; +import java.io.FileDescriptor; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.Executor; + +import sun.misc.Cleaner; + +/** + * <p>Creates an instance of a hardware-accelerated renderer. This is used to render a scene built + * from {@link RenderNode}'s to an output {@link android.view.Surface}. There can be as many + * HardwareRenderer instances as desired.</p> + * + * <h3>Resources & lifecycle</h3> + * + * <p>All HardwareRenderer instances share a common render thread. The render thread contains + * the GPU context & resources necessary to do GPU-accelerated rendering. As such, the first + * HardwareRenderer created comes with the cost of also creating the associated GPU contexts, + * however each incremental HardwareRenderer thereafter is fairly cheap. The expected usage + * is to have a HardwareRenderer instance for every active {@link Surface}. For example + * when an Activity shows a Dialog the system internally will use 2 hardware renderers, both + * of which may be drawing at the same time.</p> + * + * <p>NOTE: Due to the shared, cooperative nature of the render thread it is critical that + * any {@link Surface} used must have a prompt, reliable consuming side. System-provided + * consumers such as {@link android.view.SurfaceView}, + * {@link android.view.Window#takeSurface(SurfaceHolder.Callback2)}, + * or {@link android.view.TextureView} all fit this requirement. However if custom consumers + * are used such as when using {@link SurfaceTexture} or {@link android.media.ImageReader} + * it is the app's responsibility to ensure that they consume updates promptly and rapidly. + * Failure to do so will cause the render thread to stall on that surface, blocking all + * HardwareRenderer instances.</p> + */ +public class HardwareRenderer { + private static final String LOG_TAG = "HardwareRenderer"; + + // Keep in sync with DrawFrameTask.h SYNC_* flags + /** + * Nothing interesting to report. Sync & draw kicked off + */ + public static final int SYNC_OK = 0; + + /** + * The renderer is requesting a redraw. This can occur if there's an animation that's running + * in the RenderNode tree and the hardware renderer is unable to self-animate. + * + * <p>If this is returned from syncAndDraw the expectation is that syncAndDraw + * will be called again on the next vsync signal. + */ + public static final int SYNC_REDRAW_REQUESTED = 1 << 0; + + /** + * The hardware renderer no longer has a valid {@link android.view.Surface} to render to. + * This can happen if {@link Surface#release()} was called. The user should no longer + * attempt to call syncAndDraw until a new surface has been provided by calling + * setSurface. + * + * <p>Spoiler: the reward is GPU-accelerated drawing, better find that Surface! + */ + public static final int SYNC_LOST_SURFACE_REWARD_IF_FOUND = 1 << 1; + + /** + * The hardware renderer has been set to a "stopped" state. If this is returned then the + * rendering content has been synced, however a frame was not produced. + */ + public static final int SYNC_CONTEXT_IS_STOPPED = 1 << 2; + + /** + * The content was synced but the renderer has declined to produce a frame in this vsync + * interval. This can happen if a frame was already drawn in this vsync or if the renderer + * is outrunning the frame consumer. The renderer will internally re-schedule itself + * to render a frame in the next vsync signal, so the caller does not need to do anything + * in response to this signal. + */ + public static final int SYNC_FRAME_DROPPED = 1 << 3; + + /** @hide */ + @IntDef(value = { + SYNC_OK, SYNC_REDRAW_REQUESTED, SYNC_LOST_SURFACE_REWARD_IF_FOUND, + SYNC_CONTEXT_IS_STOPPED, SYNC_FRAME_DROPPED}) + @Retention(RetentionPolicy.SOURCE) + public @interface SyncAndDrawResult { + } + + /** @hide */ + public static final int FLAG_DUMP_FRAMESTATS = 1 << 0; + /** @hide */ + public static final int FLAG_DUMP_RESET = 1 << 1; + /** @hide */ + public static final int FLAG_DUMP_ALL = FLAG_DUMP_FRAMESTATS; + + /** @hide */ + @IntDef(flag = true, prefix = {"FLAG_DUMP_"}, value = { + FLAG_DUMP_FRAMESTATS, + FLAG_DUMP_RESET + }) + @Retention(RetentionPolicy.SOURCE) + public @interface DumpFlags { + } + + /** + * Name of the file that holds the shaders cache. + */ + private static final String CACHE_PATH_SHADERS = "com.android.opengl.shaders_cache"; + private static final String CACHE_PATH_SKIASHADERS = "com.android.skia.shaders_cache"; + + private final long mNativeProxy; + /** @hide */ + protected RenderNode mRootNode; + private boolean mOpaque = true; + private boolean mForceDark = false; + private boolean mIsWideGamut = false; + + /** + * Creates a new instance of a HardwareRenderer. The HardwareRenderer will default + * to opaque with no light source configured. + */ + public HardwareRenderer() { + mRootNode = RenderNode.adopt(nCreateRootRenderNode()); + mRootNode.setClipToBounds(false); + mNativeProxy = nCreateProxy(!mOpaque, mRootNode.mNativeRenderNode); + if (mNativeProxy == 0) { + throw new OutOfMemoryError("Unable to create hardware renderer"); + } + Cleaner.create(this, new DestroyContextRunnable(mNativeProxy)); + ProcessInitializer.sInstance.init(mNativeProxy); + } + + /** + * Destroys the rendering context of this HardwareRenderer. This destroys the resources + * associated with this renderer and releases the currently set {@link Surface}. This must + * be called when this HardwareRenderer is no longer needed. + * + * <p>The renderer may be restored from this state by setting a new {@link Surface}, setting + * new rendering content with {@link #setContentRoot(RenderNode)}, and resuming + * rendering by issuing a new {@link FrameRenderRequest}. + * + * <p>It is recommended to call this in response to callbacks such as + * {@link android.view.SurfaceHolder.Callback#surfaceDestroyed(SurfaceHolder)}. + * + * <p>Note that if there are any outstanding frame commit callbacks they may never being + * invoked if the frame was deferred to a later vsync. + */ + public void destroy() { + nDestroy(mNativeProxy, mRootNode.mNativeRenderNode); + } + + /** + * Sets a name for this renderer. This is used to identify this renderer instance + * when reporting debug information such as the per-window frame time metrics + * reported by 'adb shell dumpsys gfxinfo [package] framestats' + * + * @param name The debug name to use for this HardwareRenderer instance + */ + public void setName(@NonNull String name) { + nSetName(mNativeProxy, name); + } + + /** + * Sets the center of the light source. The light source point controls the directionality + * and shape of shadows rendered by RenderNode Z & elevation. + * + * <p>The platform's recommendation is to set lightX to 'displayWidth / 2f - windowLeft', set + * lightY to 0 - windowTop, lightZ set to 600dp, and lightRadius to 800dp. + * + * <p>The light source should be setup both as part of initial configuration, and whenever + * the window moves to ensure the light source stays anchored in display space instead + * of in window space. + * + * <p>This must be set at least once along with {@link #setLightSourceAlpha(float, float)} + * before shadows will work. + * + * @param lightX The X position of the light source + * @param lightY The Y position of the light source + * @param lightZ The Z position of the light source. Must be >= 0. + * @param lightRadius The radius of the light source. Smaller radius will have sharper edges, + * larger radius will have softer shadows. + */ + public void setLightSourceGeometry(float lightX, float lightY, float lightZ, + float lightRadius) { + validateFinite(lightX, "lightX"); + validateFinite(lightY, "lightY"); + validatePositive(lightZ, "lightZ"); + validatePositive(lightRadius, "lightRadius"); + nSetLightGeometry(mNativeProxy, lightX, lightY, lightZ, lightRadius); + } + + /** + * Configures the ambient & spot shadow alphas. This is the alpha used when the shadow + * has max alpha, and ramps down from the values provided to zero. + * + * <p>These values are typically provided by the current theme, see + * {@link android.R.attr#spotShadowAlpha} and {@link android.R.attr#ambientShadowAlpha}. + * + * <p>This must be set at least once along with + * {@link #setLightSourceGeometry(float, float, float, float)} before shadows will work. + * + * @param ambientShadowAlpha The alpha for the ambient shadow. If unsure, a reasonable default + * is 0.039f. + * @param spotShadowAlpha The alpha for the spot shadow. If unsure, a reasonable default is + * 0.19f. + */ + public void setLightSourceAlpha(@FloatRange(from = 0.0f, to = 1.0f) float ambientShadowAlpha, + @FloatRange(from = 0.0f, to = 1.0f) float spotShadowAlpha) { + validateAlpha(ambientShadowAlpha, "ambientShadowAlpha"); + validateAlpha(spotShadowAlpha, "spotShadowAlpha"); + nSetLightAlpha(mNativeProxy, ambientShadowAlpha, spotShadowAlpha); + } + + /** + * Sets the content root to render. It is not necessary to call this whenever the content + * recording changes. Any mutations to the RenderNode content, or any of the RenderNode's + * contained within the content node, will be applied whenever a new {@link FrameRenderRequest} + * is issued via {@link #createRenderRequest()} and {@link FrameRenderRequest#syncAndDraw()}. + * + * @param content The content to set as the root RenderNode. If null the content root is removed + * and the renderer will draw nothing. + */ + public void setContentRoot(@Nullable RenderNode content) { + RecordingCanvas canvas = mRootNode.beginRecording(); + if (content != null) { + canvas.drawRenderNode(content); + } + mRootNode.endRecording(); + } + + /** + * <p>The surface to render into. The surface is assumed to be associated with the display and + * as such is still driven by vsync signals such as those from + * {@link android.view.Choreographer} and that it has a native refresh rate matching that of + * the display's (typically 60hz).</p> + * + * <p>NOTE: Due to the shared, cooperative nature of the render thread it is critical that + * any {@link Surface} used must have a prompt, reliable consuming side. System-provided + * consumers such as {@link android.view.SurfaceView}, + * {@link android.view.Window#takeSurface(SurfaceHolder.Callback2)}, + * or {@link android.view.TextureView} all fit this requirement. However if custom consumers + * are used such as when using {@link SurfaceTexture} or {@link android.media.ImageReader} + * it is the app's responsibility to ensure that they consume updates promptly and rapidly. + * Failure to do so will cause the render thread to stall on that surface, blocking all + * HardwareRenderer instances.</p> + * + * @param surface The surface to render into. If null then rendering will be stopped. If + * non-null then {@link Surface#isValid()} must be true. + */ + public void setSurface(@Nullable Surface surface) { + if (surface != null && !surface.isValid()) { + throw new IllegalArgumentException("Surface is invalid. surface.isValid() == false."); + } + nSetSurface(mNativeProxy, surface); + } + + /** + * Sets the parameters that can be used to control a render request for a + * {@link HardwareRenderer}. This is not thread-safe and must not be held on to for longer + * than a single frame request. + */ + public final class FrameRenderRequest { + private FrameInfo mFrameInfo = new FrameInfo(); + private boolean mWaitForPresent; + + private FrameRenderRequest() { } + + private void reset() { + mWaitForPresent = false; + // Default to the animation time which, if choreographer is in play, will default to the + // current vsync time. Otherwise it will be 'now'. + mRenderRequest.setVsyncTime( + AnimationUtils.currentAnimationTimeMillis() * TimeUtils.NANOS_PER_MS); + } + + /** @hide */ + public void setFrameInfo(FrameInfo info) { + System.arraycopy(info.frameInfo, 0, mFrameInfo.frameInfo, 0, info.frameInfo.length); + } + + /** + * Sets the vsync time that represents the start point of this frame. Typically this + * comes from {@link android.view.Choreographer.FrameCallback}. Other compatible time + * sources include {@link System#nanoTime()}, however if the result is being displayed + * on-screen then using {@link android.view.Choreographer} is strongly recommended to + * ensure smooth animations. + * + * <p>If the clock source is not from a CLOCK_MONOTONIC source then any animations driven + * directly by RenderThread will not be synchronized properly with the current frame. + * + * @param vsyncTime The vsync timestamp for this frame. The timestamp is in nanoseconds + * and should come from a CLOCK_MONOTONIC source. + * + * @return this instance + */ + public @NonNull FrameRenderRequest setVsyncTime(long vsyncTime) { + mFrameInfo.setVsync(vsyncTime, vsyncTime); + mFrameInfo.addFlags(FrameInfo.FLAG_SURFACE_CANVAS); + return this; + } + + /** + * Adds a frame commit callback. This callback will be invoked when the current rendering + * content has been rendered into a frame and submitted to the swap chain. The frame may + * not currently be visible on the display when this is invoked, but it has been submitted. + * This callback is useful in combination with {@link PixelCopy} to capture the current + * rendered content of the UI reliably. + * + * @param executor The executor to run the callback on. It is strongly recommended that + * this executor post to a different thread, as the calling thread is + * highly sensitive to being blocked. + * @param frameCommitCallback The callback to invoke when the frame content has been drawn. + * Will be invoked on the given {@link Executor}. + * + * @return this instance + */ + public @NonNull FrameRenderRequest setFrameCommitCallback(@NonNull Executor executor, + @NonNull Runnable frameCommitCallback) { + setFrameCompleteCallback(frameNr -> executor.execute(frameCommitCallback)); + return this; + } + + /** + * Sets whether or not {@link #syncAndDraw()} should block until the frame has been + * presented. If this is true and {@link #syncAndDraw()} does not return + * {@link #SYNC_FRAME_DROPPED} or an error then when {@link #syncAndDraw()} has returned + * the frame has been submitted to the {@link Surface}. The default and typically + * recommended value is false, as blocking for present will prevent pipelining from + * happening, reducing overall throughput. This is useful for situations such as + * {@link SurfaceHolder.Callback2#surfaceRedrawNeeded(SurfaceHolder)} where it is desired + * to block until a frame has been presented to ensure first-frame consistency with + * other Surfaces. + * + * @param shouldWait If true the next call to {@link #syncAndDraw()} will block until + * completion. + * @return this instance + */ + public @NonNull FrameRenderRequest setWaitForPresent(boolean shouldWait) { + mWaitForPresent = shouldWait; + return this; + } + + /** + * Syncs the RenderNode tree to the render thread and requests a frame to be drawn. This + * {@link FrameRenderRequest} instance should no longer be used after calling this method. + * The system internally may reuse instances of {@link FrameRenderRequest} to reduce + * allocation churn. + * + * @return The result of the sync operation. + */ + @SyncAndDrawResult + public int syncAndDraw() { + int syncResult = syncAndDrawFrame(mFrameInfo); + if (mWaitForPresent && (syncResult & SYNC_FRAME_DROPPED) == 0) { + fence(); + } + return syncResult; + } + } + + private FrameRenderRequest mRenderRequest = new FrameRenderRequest(); + + /** + * Returns a {@link FrameRenderRequest} that can be used to render a new frame. This is used + * to synchronize the RenderNode content provided by {@link #setContentRoot(RenderNode)} with + * the RenderThread and then renders a single frame to the Surface set with + * {@link #setSurface(Surface)}. + * + * @return An instance of {@link FrameRenderRequest}. The instance may be reused for every + * frame, so the caller should not hold onto it for longer than a single render request. + */ + public @NonNull FrameRenderRequest createRenderRequest() { + mRenderRequest.reset(); + return mRenderRequest; + } + + /** + * Syncs the RenderNode tree to the render thread and requests a frame to be drawn. + * + * @hide + */ + @SyncAndDrawResult + public int syncAndDrawFrame(@NonNull FrameInfo frameInfo) { + return nSyncAndDrawFrame(mNativeProxy, frameInfo.frameInfo, frameInfo.frameInfo.length); + } + + /** + * Suspends any current rendering into the surface but do not do any destruction. This + * is useful to temporarily suspend using the active Surface in order to do any Surface + * mutations necessary. + * + * <p>Any subsequent draws will override the pause, resuming normal operation. + * + * @return true if there was an outstanding render request, false otherwise. If this is true + * the caller should ensure that {@link #createRenderRequest()} + * and {@link FrameRenderRequest#syncAndDraw()} is called at the soonest + * possible time to resume normal operation. + * + * TODO Should this be exposed? ViewRootImpl needs it because it destroys the old + * Surface before getting a new one. However things like SurfaceView will ensure that + * the old surface remains un-destroyed until after a new frame has been produced with + * the new surface. + * @hide + */ + public boolean pause() { + return nPause(mNativeProxy); + } + + /** + * Hard stops rendering into the surface. If the renderer is stopped it will + * block any attempt to render. Calls to {@link FrameRenderRequest#syncAndDraw()} will + * still sync over the latest rendering content, however they will not render and instead + * {@link #SYNC_CONTEXT_IS_STOPPED} will be returned. + * + * <p>If false is passed then rendering will resume as normal. Any pending rendering requests + * will produce a new frame at the next vsync signal. + * + * <p>This is useful in combination with lifecycle events such as {@link Activity#onStop()} + * and {@link Activity#onStart()}. + * + * @param stopped true to stop all rendering, false to resume + * @hide + */ + public void setStopped(boolean stopped) { + nSetStopped(mNativeProxy, stopped); + } + + /** + * Hard stops rendering into the surface. If the renderer is stopped it will + * block any attempt to render. Calls to {@link FrameRenderRequest#syncAndDraw()} will + * still sync over the latest rendering content, however they will not render and instead + * {@link #SYNC_CONTEXT_IS_STOPPED} will be returned. + * + * <p>This is useful in combination with lifecycle events such as {@link Activity#onStop()}. + * See {@link #start()} for resuming rendering. + */ + public void stop() { + nSetStopped(mNativeProxy, true); + } + + /** + * Resumes rendering into the surface. Any pending rendering requests + * will produce a new frame at the next vsync signal. + * + * <p>This is useful in combination with lifecycle events such as {@link Activity#onStart()}. + * See {@link #stop()} for stopping rendering. + */ + public void start() { + nSetStopped(mNativeProxy, false); + } + + /** + * Destroys all the display lists associated with the current rendering content. + * This includes releasing a reference to the current content root RenderNode. It will + * therefore be necessary to call {@link #setContentRoot(RenderNode)} in order to resume + * rendering after calling this, along with re-recording the display lists for the + * RenderNode tree. + * + * <p>It is recommended, but not necessary, to use this in combination with lifecycle events + * such as {@link Activity#onStop()} and {@link Activity#onStart()} or in response to + * {@link android.content.ComponentCallbacks2#onTrimMemory(int)} signals such as + * {@link android.content.ComponentCallbacks2#TRIM_MEMORY_UI_HIDDEN} + * + * See also {@link #stop()}. + */ + public void clearContent() { + nDestroyHardwareResources(mNativeProxy); + } + + /** + * Whether or not the force-dark feature should be used for this renderer. + * @hide + */ + public boolean setForceDark(boolean enable) { + if (mForceDark != enable) { + mForceDark = enable; + nSetForceDark(mNativeProxy, enable); + return true; + } + return false; + } + + /** + * Allocate buffers ahead of time to avoid allocation delays during rendering. + * + * <p>Typically a Surface will allocate buffers lazily. This is usually fine and reduces the + * memory usage of Surfaces that render rarely or never hit triple buffering. However + * for UI it can result in a slight bit of jank on first launch. This hint will + * tell the HardwareRenderer that now is a good time to allocate the 3 buffers + * necessary for typical rendering. + * + * <p>Must be called after a {@link Surface} has been set. + * + * TODO: Figure out if we even need/want this. Should HWUI just be doing this in response + * to setSurface anyway? Vulkan swapchain makes this murky, so delay making it public + * @hide + */ + public void allocateBuffers() { + nAllocateBuffers(mNativeProxy); + } + + /** + * Notifies the hardware renderer that a call to {@link FrameRenderRequest#syncAndDraw()} will + * be coming soon. This is used to help schedule when RenderThread-driven animations will + * happen as the renderer wants to avoid producing more than one frame per vsync signal. + */ + public void notifyFramePending() { + nNotifyFramePending(mNativeProxy); + } + + /** + * Change the HardwareRenderer's opacity. Will take effect on the next frame produced. + * + * <p>If the renderer is set to opaque it is the app's responsibility to ensure that the + * content renders to every pixel of the Surface, otherwise corruption may result. Note that + * this includes ensuring that the first draw of any given pixel does not attempt to blend + * against the destination. If this is false then the hardware renderer will clear to + * transparent at the start of every frame. + * + * @param opaque true if the content rendered is opaque, false if the renderer should clear + * to transparent before rendering + */ + public void setOpaque(boolean opaque) { + if (mOpaque != opaque) { + mOpaque = opaque; + nSetOpaque(mNativeProxy, mOpaque); + } + } + + /** + * Whether or not the renderer is set to be opaque. See {@link #setOpaque(boolean)} + * + * @return true if the renderer is opaque, false otherwise + */ + public boolean isOpaque() { + return mOpaque; + } + + /** @hide */ + public void setFrameCompleteCallback(FrameCompleteCallback callback) { + nSetFrameCompleteCallback(mNativeProxy, callback); + } + + /** + * TODO: Public API this? + * + * @hide + */ + public void addFrameMetricsObserver(FrameMetricsObserver observer) { + long nativeObserver = nAddFrameMetricsObserver(mNativeProxy, observer); + observer.mNative = new VirtualRefBasePtr(nativeObserver); + } + + /** + * TODO: Public API this? + * + * @hide + */ + public void removeFrameMetricsObserver(FrameMetricsObserver observer) { + nRemoveFrameMetricsObserver(mNativeProxy, observer.mNative.get()); + observer.mNative = null; + } + + /** + * Enable/disable wide gamut rendering on this renderer. Whether or not the actual rendering + * will be wide gamut depends on the hardware support for such rendering. + * + * @param wideGamut true if this renderer should render in wide gamut, false if it should + * render in sRGB + * TODO: Figure out color... + * @hide + */ + public void setWideGamut(boolean wideGamut) { + mIsWideGamut = wideGamut; + nSetWideGamut(mNativeProxy, wideGamut); + } + + /** + * Blocks until all previously queued work has completed. + * + * TODO: Only used for draw finished listeners, but the FrameCompleteCallback does that + * better + * + * @hide + */ + public void fence() { + nFence(mNativeProxy); + } + + /** @hide */ + public void registerAnimatingRenderNode(RenderNode animator) { + nRegisterAnimatingRenderNode(mRootNode.mNativeRenderNode, animator.mNativeRenderNode); + } + + /** @hide */ + public void registerVectorDrawableAnimator(NativeVectorDrawableAnimator animator) { + nRegisterVectorDrawableAnimator(mRootNode.mNativeRenderNode, + animator.getAnimatorNativePtr()); + } + + /** + * Prevents any further drawing until {@link FrameRenderRequest#syncAndDraw()} is called. + * This is a signal that the contents of the RenderNode tree are no longer safe to play back. + * In practice this usually means that there are Functor pointers in the + * display list that are no longer valid. + * + * TODO: Can we get webview off of this? + * + * @hide + */ + public void stopDrawing() { + nStopDrawing(mNativeProxy); + } + + /** + * Creates a new hardware layer. A hardware layer built by calling this + * method will be treated as a texture layer, instead of as a render target. + * + * @return A hardware layer + * @hide + */ + public TextureLayer createTextureLayer() { + long layer = nCreateTextureLayer(mNativeProxy); + return TextureLayer.adoptTextureLayer(this, layer); + } + + /** + * Detaches the layer's surface texture from the GL context and releases + * the texture id + * + * @hide + */ + public void detachSurfaceTexture(long hardwareLayer) { + nDetachSurfaceTexture(mNativeProxy, hardwareLayer); + } + + + /** @hide */ + public void buildLayer(RenderNode node) { + if (node.hasDisplayList()) { + nBuildLayer(mNativeProxy, node.mNativeRenderNode); + } + } + + /** @hide */ + public boolean copyLayerInto(final TextureLayer layer, final Bitmap bitmap) { + return nCopyLayerInto(mNativeProxy, layer.getDeferredLayerUpdater(), + bitmap.getNativeInstance()); + } + + /** + * Indicates that the specified hardware layer needs to be updated + * as soon as possible. + * + * @param layer The hardware layer that needs an update + * @hide + */ + public void pushLayerUpdate(TextureLayer layer) { + nPushLayerUpdate(mNativeProxy, layer.getDeferredLayerUpdater()); + } + + /** + * Tells the HardwareRenderer that the layer is destroyed. The renderer + * should remove the layer from any update queues. + * + * @hide + */ + public void onLayerDestroyed(TextureLayer layer) { + nCancelLayerUpdate(mNativeProxy, layer.getDeferredLayerUpdater()); + } + + /** @hide */ + public void setFrameCallback(FrameDrawingCallback callback) { + nSetFrameCallback(mNativeProxy, callback); + } + + /** + * Adds a rendernode to the renderer which can be drawn and changed asynchronously to the + * rendernode of the UI thread. + * + * @param node The node to add. + * @param placeFront If true, the render node will be placed in front of the content node, + * otherwise behind the content node. + * @hide + */ + public void addRenderNode(RenderNode node, boolean placeFront) { + nAddRenderNode(mNativeProxy, node.mNativeRenderNode, placeFront); + } + + /** + * Only especially added render nodes can be removed. + * + * @param node The node which was added via addRenderNode which should get removed again. + * @hide + */ + public void removeRenderNode(RenderNode node) { + nRemoveRenderNode(mNativeProxy, node.mNativeRenderNode); + } + + /** + * Draws a particular render node. If the node is not the content node, only the additional + * nodes will get drawn and the content remains untouched. + * + * @param node The node to be drawn. + * @hide + */ + public void drawRenderNode(RenderNode node) { + nDrawRenderNode(mNativeProxy, node.mNativeRenderNode); + } + + /** + * Loads system properties used by the renderer. This method is invoked + * whenever system properties are modified. Implementations can use this + * to trigger live updates of the renderer based on properties. + * + * @return True if a property has changed. + * @hide + */ + public boolean loadSystemProperties() { + return nLoadSystemProperties(mNativeProxy); + } + + /** + * @hide + */ + public void dumpProfileInfo(FileDescriptor fd, @DumpFlags int dumpFlags) { + nDumpProfileInfo(mNativeProxy, fd, dumpFlags); + } + + /** + * To avoid unnecessary overdrawing of the main content all additionally passed render nodes + * will be prevented to overdraw this area. It will be synchronized with the draw call. + * This should be updated in the content view's draw call. + * + * @param left The left side of the protected bounds. + * @param top The top side of the protected bounds. + * @param right The right side of the protected bounds. + * @param bottom The bottom side of the protected bounds. + * @hide + */ + public void setContentDrawBounds(int left, int top, int right, int bottom) { + nSetContentDrawBounds(mNativeProxy, left, top, right, bottom); + } + + /** @hide */ + public void setPictureCaptureCallback(@Nullable PictureCapturedCallback callback) { + nSetPictureCaptureCallback(mNativeProxy, callback); + } + + /** @hide */ + public boolean isWideGamut() { + return mIsWideGamut; + } + + /** called by native */ + static void invokePictureCapturedCallback(long picturePtr, PictureCapturedCallback callback) { + Picture picture = new Picture(picturePtr); + callback.onPictureCaptured(picture); + } + + /** + * Interface used to receive callbacks when a frame is being drawn. + * + * @hide + */ + public interface FrameDrawingCallback { + /** + * Invoked during a frame drawing. + * + * @param frame The id of the frame being drawn. + */ + void onFrameDraw(long frame); + } + + /** + * Interface used to be notified when a frame has finished rendering + * + * @hide + */ + public interface FrameCompleteCallback { + /** + * Invoked after a frame draw + * + * @param frameNr The id of the frame that was drawn. + */ + void onFrameComplete(long frameNr); + } + + /** + * Interface for listening to picture captures + * @hide + */ + public interface PictureCapturedCallback { + /** @hide */ + void onPictureCaptured(Picture picture); + } + + private static void validateAlpha(float alpha, String argumentName) { + if (!(alpha >= 0.0f && alpha <= 1.0f)) { + throw new IllegalArgumentException(argumentName + " must be a valid alpha, " + + alpha + " is not in the range of 0.0f to 1.0f"); + } + } + + private static void validatePositive(float f, String argumentName) { + if (!(Float.isFinite(f) && f >= 0.0f)) { + throw new IllegalArgumentException(argumentName + + " must be a finite positive, given=" + f); + } + } + + private static void validateFinite(float f, String argumentName) { + if (!Float.isFinite(f)) { + throw new IllegalArgumentException(argumentName + " must be finite, given=" + f); + } + } + + /** @hide */ + public static void invokeFunctor(long functor, boolean waitForCompletion) { + nInvokeFunctor(functor, waitForCompletion); + } + + /** + * b/68769804: For low FPS experiments. + * + * @hide + */ + public static void setFPSDivisor(int divisor) { + nHackySetRTAnimationsEnabled(divisor <= 1); + } + + /** + * Changes the OpenGL context priority if IMG_context_priority extension is available. Must be + * called before any OpenGL context is created. + * + * @param priority The priority to use. Must be one of EGL_CONTEXT_PRIORITY_* values. + * @hide + */ + public static void setContextPriority(int priority) { + nSetContextPriority(priority); + } + + /** + * Sets whether or not high contrast text rendering is enabled. The setting is global + * but only affects content rendered after the change is made. + * + * @hide + */ + public static void setHighContrastText(boolean highContrastText) { + nSetHighContrastText(highContrastText); + } + + /** + * If set RenderThread will avoid doing any IPC using instead a fake vsync & DisplayInfo source + * + * @hide + */ + public static void setIsolatedProcess(boolean isIsolated) { + nSetIsolatedProcess(isIsolated); + } + + /** + * If set extra graphics debugging abilities will be enabled such as dumping skp + * + * @hide + */ + public static void setDebuggingEnabled(boolean enable) { + nSetDebuggingEnabled(enable); + } + + /** @hide */ + public static int copySurfaceInto(Surface surface, Rect srcRect, Bitmap bitmap) { + if (srcRect == null) { + // Empty rect means entire surface + return nCopySurfaceInto(surface, 0, 0, 0, 0, bitmap.getNativeInstance()); + } else { + return nCopySurfaceInto(surface, srcRect.left, srcRect.top, + srcRect.right, srcRect.bottom, bitmap.getNativeInstance()); + } + } + + /** + * Creates a {@link android.graphics.Bitmap.Config#HARDWARE} bitmap from the given + * RenderNode. Note that the RenderNode should be created as a root node (so x/y of 0,0), and + * not the RenderNode from a View. + * + * @hide + **/ + public static Bitmap createHardwareBitmap(RenderNode node, int width, int height) { + return nCreateHardwareBitmap(node.mNativeRenderNode, width, height); + } + + /** + * Invoke this method when the system is running out of memory. This + * method will attempt to recover as much memory as possible, based on + * the specified hint. + * + * @param level Hint about the amount of memory that should be trimmed, + * see {@link android.content.ComponentCallbacks} + * @hide + */ + public static void trimMemory(int level) { + nTrimMemory(level); + } + + /** @hide */ + public static void overrideProperty(@NonNull String name, @NonNull String value) { + if (name == null || value == null) { + throw new IllegalArgumentException("name and value must be non-null"); + } + nOverrideProperty(name, value); + } + + /** + * Sets the directory to use as a persistent storage for threaded rendering + * resources. + * + * @param cacheDir A directory the current process can write to + * @hide + */ + public static void setupDiskCache(File cacheDir) { + setupShadersDiskCache(new File(cacheDir, CACHE_PATH_SHADERS).getAbsolutePath(), + new File(cacheDir, CACHE_PATH_SKIASHADERS).getAbsolutePath()); + } + + /** @hide */ + public static void setPackageName(String packageName) { + ProcessInitializer.sInstance.setPackageName(packageName); + } + + private static final class DestroyContextRunnable implements Runnable { + private final long mNativeInstance; + + DestroyContextRunnable(long nativeInstance) { + mNativeInstance = nativeInstance; + } + + @Override + public void run() { + nDeleteProxy(mNativeInstance); + } + } + + private static class ProcessInitializer { + static ProcessInitializer sInstance = new ProcessInitializer(); + + private boolean mInitialized = false; + + private String mPackageName; + private IGraphicsStats mGraphicsStatsService; + private IGraphicsStatsCallback mGraphicsStatsCallback = new IGraphicsStatsCallback.Stub() { + @Override + public void onRotateGraphicsStatsBuffer() throws RemoteException { + rotateBuffer(); + } + }; + + private ProcessInitializer() { + } + + synchronized void setPackageName(String name) { + if (mInitialized) return; + mPackageName = name; + } + + synchronized void init(long renderProxy) { + if (mInitialized) return; + mInitialized = true; + + initSched(renderProxy); + initGraphicsStats(); + } + + private void initSched(long renderProxy) { + try { + int tid = nGetRenderThreadTid(renderProxy); + ActivityManager.getService().setRenderThread(tid); + } catch (Throwable t) { + Log.w(LOG_TAG, "Failed to set scheduler for RenderThread", t); + } + } + + private void initGraphicsStats() { + if (mPackageName == null) return; + + try { + IBinder binder = ServiceManager.getService("graphicsstats"); + if (binder == null) return; + mGraphicsStatsService = IGraphicsStats.Stub.asInterface(binder); + requestBuffer(); + } catch (Throwable t) { + Log.w(LOG_TAG, "Could not acquire gfx stats buffer", t); + } + } + + private void rotateBuffer() { + nRotateProcessStatsBuffer(); + requestBuffer(); + } + + private void requestBuffer() { + try { + ParcelFileDescriptor pfd = mGraphicsStatsService + .requestBufferForProcess(mPackageName, mGraphicsStatsCallback); + nSetProcessStatsBuffer(pfd.getFd()); + pfd.close(); + } catch (Throwable t) { + Log.w(LOG_TAG, "Could not acquire gfx stats buffer", t); + } + } + } + + /** + * @hide + */ + public static native void disableVsync(); + + /** + * Start render thread and initialize EGL or Vulkan. + * + * Initializing EGL involves loading and initializing the graphics driver. Some drivers take + * several 10s of milliseconds to do this, so doing it on-demand when an app tries to render + * its first frame adds directly to user-visible app launch latency. + * + * Should only be called after GraphicsEnvironment.chooseDriver(). + * @hide + */ + public static native void preload(); + + /** @hide */ + protected static native void setupShadersDiskCache(String cacheFile, String skiaCacheFile); + + private static native void nRotateProcessStatsBuffer(); + + private static native void nSetProcessStatsBuffer(int fd); + + private static native int nGetRenderThreadTid(long nativeProxy); + + private static native long nCreateRootRenderNode(); + + private static native long nCreateProxy(boolean translucent, long rootRenderNode); + + private static native void nDeleteProxy(long nativeProxy); + + private static native boolean nLoadSystemProperties(long nativeProxy); + + private static native void nSetName(long nativeProxy, String name); + + private static native void nSetSurface(long nativeProxy, Surface window); + + private static native boolean nPause(long nativeProxy); + + private static native void nSetStopped(long nativeProxy, boolean stopped); + + private static native void nSetLightGeometry(long nativeProxy, + float lightX, float lightY, float lightZ, float lightRadius); + + private static native void nSetLightAlpha(long nativeProxy, float ambientShadowAlpha, + float spotShadowAlpha); + + private static native void nSetOpaque(long nativeProxy, boolean opaque); + + private static native void nSetWideGamut(long nativeProxy, boolean wideGamut); + + private static native int nSyncAndDrawFrame(long nativeProxy, long[] frameInfo, int size); + + private static native void nDestroy(long nativeProxy, long rootRenderNode); + + private static native void nRegisterAnimatingRenderNode(long rootRenderNode, + long animatingNode); + + private static native void nRegisterVectorDrawableAnimator(long rootRenderNode, long animator); + + private static native void nInvokeFunctor(long functor, boolean waitForCompletion); + + private static native long nCreateTextureLayer(long nativeProxy); + + private static native void nBuildLayer(long nativeProxy, long node); + + private static native boolean nCopyLayerInto(long nativeProxy, long layer, long bitmapHandle); + + private static native void nPushLayerUpdate(long nativeProxy, long layer); + + private static native void nCancelLayerUpdate(long nativeProxy, long layer); + + private static native void nDetachSurfaceTexture(long nativeProxy, long layer); + + private static native void nDestroyHardwareResources(long nativeProxy); + + private static native void nTrimMemory(int level); + + private static native void nOverrideProperty(String name, String value); + + private static native void nFence(long nativeProxy); + + private static native void nStopDrawing(long nativeProxy); + + private static native void nNotifyFramePending(long nativeProxy); + + private static native void nDumpProfileInfo(long nativeProxy, FileDescriptor fd, + @DumpFlags int dumpFlags); + + private static native void nAddRenderNode(long nativeProxy, long rootRenderNode, + boolean placeFront); + + private static native void nRemoveRenderNode(long nativeProxy, long rootRenderNode); + + private static native void nDrawRenderNode(long nativeProxy, long rootRenderNode); + + private static native void nSetContentDrawBounds(long nativeProxy, int left, + int top, int right, int bottom); + + private static native void nSetPictureCaptureCallback(long nativeProxy, + PictureCapturedCallback callback); + + private static native void nSetFrameCallback(long nativeProxy, FrameDrawingCallback callback); + + private static native void nSetFrameCompleteCallback(long nativeProxy, + FrameCompleteCallback callback); + + private static native long nAddFrameMetricsObserver(long nativeProxy, + FrameMetricsObserver observer); + + private static native void nRemoveFrameMetricsObserver(long nativeProxy, long nativeObserver); + + private static native int nCopySurfaceInto(Surface surface, + int srcLeft, int srcTop, int srcRight, int srcBottom, long bitmapHandle); + + private static native Bitmap nCreateHardwareBitmap(long renderNode, int width, int height); + + private static native void nSetHighContrastText(boolean enabled); + + // For temporary experimentation b/66945974 + private static native void nHackySetRTAnimationsEnabled(boolean enabled); + + private static native void nSetDebuggingEnabled(boolean enabled); + + private static native void nSetIsolatedProcess(boolean enabled); + + private static native void nSetContextPriority(int priority); + + private static native void nAllocateBuffers(long nativeProxy); + + private static native void nSetForceDark(long nativeProxy, boolean enabled); +} diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java index 098f10003b8a..2d5babc5ebdb 100644 --- a/graphics/java/android/graphics/ImageDecoder.java +++ b/graphics/java/android/graphics/ImageDecoder.java @@ -28,6 +28,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Px; import android.annotation.TestApi; +import android.annotation.UnsupportedAppUsage; import android.annotation.WorkerThread; import android.content.ContentResolver; import android.content.res.AssetFileDescriptor; @@ -58,6 +59,9 @@ import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Retention; import java.nio.ByteBuffer; +import java.util.Locale; +import java.util.Objects; +import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -186,7 +190,7 @@ public final class ImageDecoder implements AutoCloseable { * {@link OnHeaderDecodedListener OnHeaderDecodedListener} and once with an * implementation of {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded} * that calls {@link #setTargetSize} with smaller dimensions. One {@code Source} - * even used simultaneously in multiple threads.</p> + * can even be used simultaneously in multiple threads.</p> */ public static abstract class Source { private Source() {} @@ -282,26 +286,7 @@ public final class ImageDecoder implements AutoCloseable { return createFromStream(is, true, this); } - - final FileDescriptor fd = assetFd.getFileDescriptor(); - final long offset = assetFd.getStartOffset(); - - ImageDecoder decoder = null; - try { - try { - Os.lseek(fd, offset, SEEK_SET); - decoder = nCreate(fd, this); - } catch (ErrnoException e) { - decoder = createFromStream(new FileInputStream(fd), true, this); - } - } finally { - if (decoder == null) { - IoUtils.closeQuietly(assetFd); - } else { - decoder.mAssetFd = assetFd; - } - } - return decoder; + return createFromAssetFileDescriptor(assetFd, this); } } @@ -353,6 +338,30 @@ public final class ImageDecoder implements AutoCloseable { return decoder; } + @NonNull + private static ImageDecoder createFromAssetFileDescriptor(@NonNull AssetFileDescriptor assetFd, + Source source) throws IOException { + final FileDescriptor fd = assetFd.getFileDescriptor(); + final long offset = assetFd.getStartOffset(); + + ImageDecoder decoder = null; + try { + try { + Os.lseek(fd, offset, SEEK_SET); + decoder = nCreate(fd, source); + } catch (ErrnoException e) { + decoder = createFromStream(new FileInputStream(fd), true, source); + } + } finally { + if (decoder == null) { + IoUtils.closeQuietly(assetFd); + } else { + decoder.mAssetFd = assetFd; + } + } + return decoder; + } + /** * For backwards compatibility, this does *not* close the InputStream. * @@ -365,7 +374,7 @@ public final class ImageDecoder implements AutoCloseable { } mResources = res; mInputStream = is; - mInputDensity = res != null ? inputDensity : Bitmap.DENSITY_NONE; + mInputDensity = inputDensity; } final Resources mResources; @@ -527,6 +536,29 @@ public final class ImageDecoder implements AutoCloseable { } } + private static class CallableSource extends Source { + CallableSource(@NonNull Callable<AssetFileDescriptor> callable) { + mCallable = callable; + } + + private final Callable<AssetFileDescriptor> mCallable; + + @Override + public ImageDecoder createImageDecoder() throws IOException { + AssetFileDescriptor assetFd = null; + try { + assetFd = mCallable.call(); + } catch (Exception e) { + if (e instanceof IOException) { + throw (IOException) e; + } else { + throw new IOException(e); + } + } + return createFromAssetFileDescriptor(assetFd, this); + } + } + /** * Information about an encoded image. */ @@ -808,6 +840,40 @@ public final class ImageDecoder implements AutoCloseable { } /** + * Return if the given MIME type is a supported file format that can be + * decoded by this class. This can be useful to determine if a file can be + * decoded directly, or if it needs to be converted into a more general + * format using an API like {@link ContentResolver#openTypedAssetFile}. + */ + public static boolean isMimeTypeSupported(@NonNull String mimeType) { + Objects.requireNonNull(mimeType); + switch (mimeType.toLowerCase(Locale.US)) { + case "image/png": + case "image/jpeg": + case "image/webp": + case "image/gif": + case "image/heif": + case "image/heic": + case "image/bmp": + case "image/x-ico": + case "image/vnd.wap.wbmp": + case "image/x-sony-arw": + case "image/x-canon-cr2": + case "image/x-adobe-dng": + case "image/x-nikon-nef": + case "image/x-nikon-nrw": + case "image/x-olympus-orf": + case "image/x-fuji-raf": + case "image/x-panasonic-rw2": + case "image/x-pentax-pef": + case "image/x-samsung-srw": + return true; + default: + return false; + } + } + + /** * Create a new {@link Source Source} from a resource. * * @param res the {@link Resources} object containing the image data. @@ -970,6 +1036,27 @@ public final class ImageDecoder implements AutoCloseable { } /** + * Create a new {@link Source Source} from a {@link Callable} that returns a + * new {@link AssetFileDescriptor} for each request. This provides control + * over how the {@link AssetFileDescriptor} is created, such as passing + * options into {@link ContentResolver#openTypedAssetFileDescriptor}, or + * enabling use of a {@link android.os.CancellationSignal}. + * <p> + * It's important for the given {@link Callable} to return a new, unique + * {@link AssetFileDescriptor} for each invocation, to support reuse of the + * returned {@link Source Source}. + * + * @return a new Source object, which can be passed to + * {@link #decodeDrawable decodeDrawable} or {@link #decodeBitmap + * decodeBitmap}. + */ + @AnyThread + @NonNull + public static Source createSource(@NonNull Callable<AssetFileDescriptor> callable) { + return new CallableSource(callable); + } + + /** * Return the width and height of a given sample size. * * <p>This takes an input that functions like @@ -1505,12 +1592,9 @@ public final class ImageDecoder implements AutoCloseable { * decoder will pick either the color space embedded in the image or the * {@link ColorSpace} best suited for the requested image configuration * (for instance {@link ColorSpace.Named#SRGB sRGB} for the - * {@link Bitmap.Config#ARGB_8888} configuration).</p> - * - * <p>{@link Bitmap.Config#RGBA_F16} always uses the - * {@link ColorSpace.Named#LINEAR_EXTENDED_SRGB scRGB} color space. - * Bitmaps in other configurations without an embedded color space are - * assumed to be in the {@link ColorSpace.Named#SRGB sRGB} color space.</p> + * {@link Bitmap.Config#ARGB_8888} configuration and + * {@link ColorSpace.Named#EXTENDED_SRGB EXTENDED_SRGB} for + * {@link Bitmap.Config#RGBA_F16}).</p> * * <p class="note">Only {@link ColorSpace.Model#RGB} color spaces are * currently supported. An <code>IllegalArgumentException</code> will @@ -1558,14 +1642,16 @@ public final class ImageDecoder implements AutoCloseable { mTempStorage = null; } - private void checkState() { + private void checkState(boolean animated) { if (mNativePtr == 0) { throw new IllegalStateException("Cannot use closed ImageDecoder!"); } checkSubset(mDesiredWidth, mDesiredHeight, mCropRect); - if (mAllocator == ALLOCATOR_HARDWARE) { + // animated ignores the allocator, so no need to check for incompatible + // fields. + if (!animated && mAllocator == ALLOCATOR_HARDWARE) { if (mMutable) { throw new IllegalStateException("Cannot make mutable HARDWARE Bitmap!"); } @@ -1577,17 +1663,6 @@ public final class ImageDecoder implements AutoCloseable { if (mPostProcessor != null && mUnpremultipliedRequired) { throw new IllegalStateException("Cannot draw to unpremultiplied pixels!"); } - - if (mDesiredColorSpace != null) { - if (!(mDesiredColorSpace instanceof ColorSpace.Rgb)) { - throw new IllegalArgumentException("The target color space must use the " - + "RGB color model - provided: " + mDesiredColorSpace); - } - if (((ColorSpace.Rgb) mDesiredColorSpace).getTransferParameters() == null) { - throw new IllegalArgumentException("The target color space must use an " - + "ICC parametric transfer function - provided: " + mDesiredColorSpace); - } - } } private static void checkSubset(int width, int height, Rect r) { @@ -1600,14 +1675,30 @@ public final class ImageDecoder implements AutoCloseable { } } + private boolean checkForExtended() { + if (mDesiredColorSpace == null) { + return false; + } + return mDesiredColorSpace == ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB) + || mDesiredColorSpace == ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB); + } + + private long getColorSpacePtr() { + if (mDesiredColorSpace == null) { + return 0; + } + return mDesiredColorSpace.getNativeInstance(); + } + @WorkerThread @NonNull private Bitmap decodeBitmapInternal() throws IOException { - checkState(); + checkState(false); return nDecodeBitmap(mNativePtr, this, mPostProcessor != null, mDesiredWidth, mDesiredHeight, mCropRect, mMutable, mAllocator, mUnpremultipliedRequired, - mConserveMemory, mDecodeAsAlphaMask, mDesiredColorSpace); + mConserveMemory, mDecodeAsAlphaMask, getColorSpacePtr(), + checkForExtended()); } private void callHeaderDecoded(@Nullable OnHeaderDecodedListener listener, @@ -1673,9 +1764,11 @@ public final class ImageDecoder implements AutoCloseable { // mPostProcessor exists. ImageDecoder postProcessPtr = decoder.mPostProcessor == null ? null : decoder; + decoder.checkState(true); Drawable d = new AnimatedImageDrawable(decoder.mNativePtr, postProcessPtr, decoder.mDesiredWidth, - decoder.mDesiredHeight, srcDensity, + decoder.mDesiredHeight, decoder.getColorSpacePtr(), + decoder.checkForExtended(), srcDensity, src.computeDstDensity(), decoder.mCropRect, decoder.mInputStream, decoder.mAssetFd); // d has taken ownership of these objects. @@ -1818,8 +1911,8 @@ public final class ImageDecoder implements AutoCloseable { } float scale = (float) dstDensity / srcDensity; - int scaledWidth = (int) (mWidth * scale + 0.5f); - int scaledHeight = (int) (mHeight * scale + 0.5f); + int scaledWidth = Math.max((int) (mWidth * scale + 0.5f), 1); + int scaledHeight = Math.max((int) (mHeight * scale + 0.5f), 1); this.setTargetSize(scaledWidth, scaledHeight); return dstDensity; } @@ -1856,6 +1949,7 @@ public final class ImageDecoder implements AutoCloseable { * Private method called by JNI. */ @SuppressWarnings("unused") + @UnsupportedAppUsage private int postProcessAndRelease(@NonNull Canvas canvas) { try { return mPostProcessor.onPostProcess(canvas); @@ -1894,7 +1988,7 @@ public final class ImageDecoder implements AutoCloseable { @Nullable Rect cropRect, boolean mutable, int allocator, boolean unpremulRequired, boolean conserveMemory, boolean decodeAsAlphaMask, - @Nullable ColorSpace desiredColorSpace) + long desiredColorSpace, boolean extended) throws IOException; private static native Size nGetSampledSize(long nativePtr, int sampleSize); diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java index 43fd2708ee3e..15d855e9560c 100644 --- a/graphics/java/android/graphics/ImageFormat.java +++ b/graphics/java/android/graphics/ImageFormat.java @@ -16,7 +16,43 @@ package android.graphics; +import android.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + public class ImageFormat { + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + UNKNOWN, + RGB_565, + YV12, + Y8, + Y16, + NV16, + NV21, + YUY2, + JPEG, + DEPTH_JPEG, + YUV_420_888, + YUV_422_888, + YUV_444_888, + FLEX_RGB_888, + FLEX_RGBA_8888, + RAW_SENSOR, + RAW_PRIVATE, + RAW10, + RAW12, + DEPTH16, + DEPTH_POINT_CLOUD, + RAW_DEPTH, + PRIVATE, + HEIC + }) + public @interface Format { + } + /* * these constants are chosen to be binary compatible with their previous * location in PixelFormat.java @@ -90,18 +126,20 @@ public class ImageFormat { * </ul> * </p> * - * <pre> y_size = stride * height </pre> + * <pre> size = stride * height </pre> * * <p>For example, the {@link android.media.Image} object can provide data - * in this format from a {@link android.hardware.camera2.CameraDevice} - * through a {@link android.media.ImageReader} object if this format is - * supported by {@link android.hardware.camera2.CameraDevice}.</p> + * in this format from a {@link android.hardware.camera2.CameraDevice} (if + * supported) through a {@link android.media.ImageReader} object. The + * {@link android.media.Image#getPlanes() Image#getPlanes()} will return a + * single plane containing the pixel data. The pixel stride is always 1 in + * {@link android.media.Image.Plane#getPixelStride()}, and the + * {@link android.media.Image.Plane#getRowStride()} describes the vertical + * neighboring pixel distance (in bytes) between adjacent rows.</p> * * @see android.media.Image * @see android.media.ImageReader * @see android.hardware.camera2.CameraDevice - * - * @hide */ public static final int Y8 = 0x20203859; @@ -181,6 +219,14 @@ public class ImageFormat { public static final int JPEG = 0x100; /** + * Depth augmented compressed JPEG format. + * + * <p>JPEG compressed main image along with XMP embedded depth metadata + * following ISO 16684-1:2011(E).</p> + */ + public static final int DEPTH_JPEG = 0x69656963; + + /** * <p>Multi-plane Android YUV 420 format</p> * * <p>This format is a generic YCbCr format, capable of describing any 4:2:0 @@ -706,6 +752,14 @@ public class ImageFormat { public static final int PRIVATE = 0x22; /** + * Compressed HEIC format. + * + * <p>This format defines the HEIC brand of High Efficiency Image File + * Format as described in ISO/IEC 23008-12.</p> + */ + public static final int HEIC = 0x48454946; + + /** * Use this function to retrieve the number of bits per pixel of an * ImageFormat. * @@ -713,7 +767,7 @@ public class ImageFormat { * @return the number of bits per pixel of the given format or -1 if the * format doesn't exist or is not supported. */ - public static int getBitsPerPixel(int format) { + public static int getBitsPerPixel(@Format int format) { switch (format) { case RGB_565: return 16; @@ -763,7 +817,7 @@ public class ImageFormat { * * @hide */ - public static boolean isPublicFormat(int format) { + public static boolean isPublicFormat(@Format int format) { switch (format) { case RGB_565: case NV16: @@ -784,6 +838,9 @@ public class ImageFormat { case DEPTH_POINT_CLOUD: case PRIVATE: case RAW_DEPTH: + case Y8: + case DEPTH_JPEG: + case HEIC: return true; } diff --git a/graphics/java/android/graphics/Insets.aidl b/graphics/java/android/graphics/Insets.aidl new file mode 100644 index 000000000000..e65e72d27f49 --- /dev/null +++ b/graphics/java/android/graphics/Insets.aidl @@ -0,0 +1,20 @@ +/* //device/java/android/android/graphics/Insets.aidl +** +** Copyright 2019, 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.graphics; + +parcelable Insets; diff --git a/graphics/java/android/graphics/Insets.java b/graphics/java/android/graphics/Insets.java index 156f9903a632..1e03c530aed7 100644 --- a/graphics/java/android/graphics/Insets.java +++ b/graphics/java/android/graphics/Insets.java @@ -16,6 +16,11 @@ package android.graphics; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + /** * An Insets instance holds four integer offsets which describe changes to the four * edges of a Rectangle. By convention, positive values move edges towards the @@ -23,10 +28,9 @@ package android.graphics; * <p> * Insets are immutable so may be treated as values. * - * @hide */ -public class Insets { - public static final Insets NONE = new Insets(0, 0, 0, 0); +public final class Insets implements Parcelable { + public static final @NonNull Insets NONE = new Insets(0, 0, 0, 0); public final int left; public final int top; @@ -52,7 +56,7 @@ public class Insets { * * @return Insets instance with the appropriate values */ - public static Insets of(int left, int top, int right, int bottom) { + public static @NonNull Insets of(int left, int top, int right, int bottom) { if (left == 0 && top == 0 && right == 0 && bottom == 0) { return NONE; } @@ -66,11 +70,66 @@ public class Insets { * * @return an Insets instance with the appropriate values */ - public static Insets of(Rect r) { + public static @NonNull Insets of(@Nullable Rect r) { return (r == null) ? NONE : of(r.left, r.top, r.right, r.bottom); } /** + * Returns a Rect instance with the appropriate values. + * + * @hide + */ + public @NonNull Rect toRect() { + return new Rect(left, top, right, bottom); + } + + /** + * Add two Insets. + * + * @param a The first Insets to add. + * @param b The second Insets to add. + * @return a + b, i. e. all insets on every side are added together. + */ + public static @NonNull Insets add(@NonNull Insets a, @NonNull Insets b) { + return Insets.of(a.left + b.left, a.top + b.top, a.right + b.right, a.bottom + b.bottom); + } + + /** + * Subtract two Insets. + * + * @param a The minuend. + * @param b The subtrahend. + * @return a - b, i. e. all insets on every side are subtracted from each other. + */ + public static @NonNull Insets subtract(@NonNull Insets a, @NonNull Insets b) { + return Insets.of(a.left - b.left, a.top - b.top, a.right - b.right, a.bottom - b.bottom); + } + + /** + * Retrieves the maximum of two Insets. + * + * @param a The first Insets. + * @param b The second Insets. + * @return max(a, b), i. e. the larger of every inset on every side is taken for the result. + */ + public static @NonNull Insets max(@NonNull Insets a, @NonNull Insets b) { + return Insets.of(Math.max(a.left, b.left), Math.max(a.top, b.top), + Math.max(a.right, b.right), Math.max(a.bottom, b.bottom)); + } + + /** + * Retrieves the minimum of two Insets. + * + * @param a The first Insets. + * @param b The second Insets. + * @return min(a, b), i. e. the smaller of every inset on every side is taken for the result. + */ + public static @NonNull Insets min(@NonNull Insets a, @NonNull Insets b) { + return Insets.of(Math.min(a.left, b.left), Math.min(a.top, b.top), + Math.min(a.right, b.right), Math.min(a.bottom, b.bottom)); + } + + /** * Two Insets instances are equal iff they belong to the same class and their fields are * pairwise equal. * @@ -111,4 +170,29 @@ public class Insets { ", bottom=" + bottom + '}'; } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(left); + out.writeInt(top); + out.writeInt(right); + out.writeInt(bottom); + } + + public static final @android.annotation.NonNull Parcelable.Creator<Insets> CREATOR = new Parcelable.Creator<Insets>() { + @Override + public Insets createFromParcel(Parcel in) { + return new Insets(in.readInt(), in.readInt(), in.readInt(), in.readInt()); + } + + @Override + public Insets[] newArray(int size) { + return new Insets[size]; + } + }; } diff --git a/graphics/java/android/graphics/LightingColorFilter.java b/graphics/java/android/graphics/LightingColorFilter.java index 1578ffb873f0..62a890ff4f0b 100644 --- a/graphics/java/android/graphics/LightingColorFilter.java +++ b/graphics/java/android/graphics/LightingColorFilter.java @@ -22,6 +22,7 @@ package android.graphics; import android.annotation.ColorInt; +import android.annotation.UnsupportedAppUsage; /** * A color filter that can be used to simulate simple lighting effects. @@ -72,6 +73,7 @@ public class LightingColorFilter extends ColorFilter { * * @hide */ + @UnsupportedAppUsage public void setColorMultiply(@ColorInt int mul) { if (mMul != mul) { mMul = mul; @@ -97,6 +99,7 @@ public class LightingColorFilter extends ColorFilter { * * @hide */ + @UnsupportedAppUsage public void setColorAdd(@ColorInt int add) { if (mAdd != add) { mAdd = add; diff --git a/graphics/java/android/graphics/LinearGradient.java b/graphics/java/android/graphics/LinearGradient.java index 7139efec9337..12e63c09d76b 100644 --- a/graphics/java/android/graphics/LinearGradient.java +++ b/graphics/java/android/graphics/LinearGradient.java @@ -17,30 +17,59 @@ package android.graphics; import android.annotation.ColorInt; +import android.annotation.ColorLong; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UnsupportedAppUsage; -public class LinearGradient extends Shader { - - private static final int TYPE_COLORS_AND_POSITIONS = 1; - private static final int TYPE_COLOR_START_AND_COLOR_END = 2; - - /** - * Type of the LinearGradient: can be either TYPE_COLORS_AND_POSITIONS or - * TYPE_COLOR_START_AND_COLOR_END. - */ - private int mType; +public class LinearGradient extends Shader { + @UnsupportedAppUsage private float mX0; + @UnsupportedAppUsage private float mY0; + @UnsupportedAppUsage private float mX1; + @UnsupportedAppUsage private float mY1; - private int[] mColors; + @UnsupportedAppUsage private float[] mPositions; + @UnsupportedAppUsage + private TileMode mTileMode; + + // @ColorInts are replaced by @ColorLongs, but these remain due to @UnsupportedAppUsage. + @UnsupportedAppUsage + @ColorInt + private int[] mColors; + @UnsupportedAppUsage + @ColorInt private int mColor0; + @UnsupportedAppUsage + @ColorInt private int mColor1; - private TileMode mTileMode; + @ColorLong + private final long[] mColorLongs; + + + /** + * Create a shader that draws a linear gradient along a line. + * + * @param x0 The x-coordinate for the start of the gradient line + * @param y0 The y-coordinate for the start of the gradient line + * @param x1 The x-coordinate for the end of the gradient line + * @param y1 The y-coordinate for the end of the gradient line + * @param colors The sRGB colors to be distributed along the gradient line + * @param positions May be null. The relative positions [0..1] of + * each corresponding color in the colors array. If this is null, + * the the colors are distributed evenly along the gradient line. + * @param tile The Shader tiling mode + */ + public LinearGradient(float x0, float y0, float x1, float y1, @NonNull @ColorInt int[] colors, + @Nullable float[] positions, @NonNull TileMode tile) { + this(x0, y0, x1, y1, convertColors(colors), positions, tile, + ColorSpace.get(ColorSpace.Named.SRGB)); + } /** * Create a shader that draws a linear gradient along a line. @@ -54,21 +83,33 @@ public class LinearGradient extends Shader { * each corresponding color in the colors array. If this is null, * the the colors are distributed evenly along the gradient line. * @param tile The Shader tiling mode - */ - public LinearGradient(float x0, float y0, float x1, float y1, @NonNull @ColorInt int colors[], - @Nullable float positions[], @NonNull TileMode tile) { - if (colors.length < 2) { - throw new IllegalArgumentException("needs >= 2 number of colors"); - } + * + * @throws IllegalArgumentException if there are less than two colors, the colors do + * not share the same {@link ColorSpace} or do not use a valid one, or {@code positions} + * is not {@code null} and has a different length from {@code colors}. + */ + public LinearGradient(float x0, float y0, float x1, float y1, @NonNull @ColorLong long[] colors, + @Nullable float[] positions, @NonNull TileMode tile) { + this(x0, y0, x1, y1, colors.clone(), positions, tile, detectColorSpace(colors)); + } + + /** + * Base constructor. Assumes @param colors is a copy that this object can hold onto, + * and all colors share @param colorSpace. + */ + private LinearGradient(float x0, float y0, float x1, float y1, + @NonNull @ColorLong long[] colors, @Nullable float[] positions, @NonNull TileMode tile, + @NonNull ColorSpace colorSpace) { + super(colorSpace); + if (positions != null && colors.length != positions.length) { throw new IllegalArgumentException("color and position arrays must be of equal length"); } - mType = TYPE_COLORS_AND_POSITIONS; mX0 = x0; mY0 = y0; mX1 = x1; mY1 = y1; - mColors = colors.clone(); + mColorLongs = colors; mPositions = positions != null ? positions.clone() : null; mTileMode = tile; } @@ -80,54 +121,43 @@ public class LinearGradient extends Shader { * @param y0 The y-coordinate for the start of the gradient line * @param x1 The x-coordinate for the end of the gradient line * @param y1 The y-coordinate for the end of the gradient line - * @param color0 The color at the start of the gradient line. - * @param color1 The color at the end of the gradient line. + * @param color0 The sRGB color at the start of the gradient line. + * @param color1 The sRGB color at the end of the gradient line. * @param tile The Shader tiling mode - */ + */ public LinearGradient(float x0, float y0, float x1, float y1, @ColorInt int color0, @ColorInt int color1, @NonNull TileMode tile) { - mType = TYPE_COLOR_START_AND_COLOR_END; - mX0 = x0; - mY0 = y0; - mX1 = x1; - mY1 = y1; - mColor0 = color0; - mColor1 = color1; - mColors = null; - mPositions = null; - mTileMode = tile; - } - - @Override - long createNativeInstance(long nativeMatrix) { - if (mType == TYPE_COLORS_AND_POSITIONS) { - return nativeCreate1(nativeMatrix, mX0, mY0, mX1, mY1, - mColors, mPositions, mTileMode.nativeInt); - } else { // TYPE_COLOR_START_AND_COLOR_END - return nativeCreate2(nativeMatrix, mX0, mY0, mX1, mY1, - mColor0, mColor1, mTileMode.nativeInt); - } + this(x0, y0, x1, y1, Color.pack(color0), Color.pack(color1), tile); } /** - * @hide + * Create a shader that draws a linear gradient along a line. + * + * @param x0 The x-coordinate for the start of the gradient line + * @param y0 The y-coordinate for the start of the gradient line + * @param x1 The x-coordinate for the end of the gradient line + * @param y1 The y-coordinate for the end of the gradient line + * @param color0 The color at the start of the gradient line. + * @param color1 The color at the end of the gradient line. + * @param tile The Shader tiling mode + * + * @throws IllegalArgumentException if the colors do + * not share the same {@link ColorSpace} or do not use a valid one. */ + public LinearGradient(float x0, float y0, float x1, float y1, + @ColorLong long color0, @ColorLong long color1, + @NonNull TileMode tile) { + this(x0, y0, x1, y1, new long[] {color0, color1}, null, tile); + } + @Override - protected Shader copy() { - final LinearGradient copy; - if (mType == TYPE_COLORS_AND_POSITIONS) { - copy = new LinearGradient(mX0, mY0, mX1, mY1, mColors.clone(), - mPositions != null ? mPositions.clone() : null, mTileMode); - } else { // TYPE_COLOR_START_AND_COLOR_END - copy = new LinearGradient(mX0, mY0, mX1, mY1, mColor0, mColor1, mTileMode); - } - copyLocalMatrix(copy); - return copy; + long createNativeInstance(long nativeMatrix) { + return nativeCreate(nativeMatrix, mX0, mY0, mX1, mY1, + mColorLongs, mPositions, mTileMode.nativeInt, + colorSpace().getNativeInstance()); } - private native long nativeCreate1(long matrix, float x0, float y0, float x1, float y1, - int colors[], float positions[], int tileMode); - private native long nativeCreate2(long matrix, float x0, float y0, float x1, float y1, - int color0, int color1, int tileMode); + private native long nativeCreate(long matrix, float x0, float y0, float x1, float y1, + long[] colors, float[] positions, int tileMode, long colorSpaceHandle); } diff --git a/graphics/java/android/graphics/Matrix.java b/graphics/java/android/graphics/Matrix.java index 486070c99e3f..22b6401fdc2e 100644 --- a/graphics/java/android/graphics/Matrix.java +++ b/graphics/java/android/graphics/Matrix.java @@ -16,6 +16,8 @@ package android.graphics; +import android.annotation.UnsupportedAppUsage; + import dalvik.annotation.optimization.CriticalNative; import dalvik.annotation.optimization.FastNative; @@ -39,6 +41,7 @@ public class Matrix { public static final int MPERSP_2 = 8; //!< use with getValues/setValues /** @hide */ + @UnsupportedAppUsage public final static Matrix IDENTITY_MATRIX = new Matrix() { void oops() { throw new IllegalStateException("Matrix can not be modified"); @@ -220,17 +223,16 @@ public class Matrix { } }; - // sizeof(SkMatrix) is 9 * sizeof(float) + uint32_t - private static final long NATIVE_ALLOCATION_SIZE = 40; - private static class NoImagePreloadHolder { - public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( - Matrix.class.getClassLoader(), nGetNativeFinalizer(), NATIVE_ALLOCATION_SIZE); + public static final NativeAllocationRegistry sRegistry = + NativeAllocationRegistry.createMalloced( + Matrix.class.getClassLoader(), nGetNativeFinalizer()); } /** * @hide */ + @UnsupportedAppUsage public final long native_instance; /** diff --git a/graphics/java/android/graphics/Movie.java b/graphics/java/android/graphics/Movie.java index 83857bece930..6f030ffac2df 100644 --- a/graphics/java/android/graphics/Movie.java +++ b/graphics/java/android/graphics/Movie.java @@ -16,7 +16,9 @@ package android.graphics; +import android.annotation.UnsupportedAppUsage; import android.content.res.AssetManager; +import android.os.Build; import java.io.FileInputStream; import java.io.InputStream; @@ -24,9 +26,12 @@ import java.io.InputStream; /** * @deprecated Prefer {@link android.graphics.drawable.AnimatedImageDrawable}. */ +@Deprecated public class Movie { + @UnsupportedAppUsage private long mNativeMovie; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private Movie(long nativeMovie) { if (nativeMovie == 0) { throw new RuntimeException("native movie creation failed"); diff --git a/graphics/java/android/graphics/NinePatch.java b/graphics/java/android/graphics/NinePatch.java index b6a209f25df9..c4c1eaceb4fc 100644 --- a/graphics/java/android/graphics/NinePatch.java +++ b/graphics/java/android/graphics/NinePatch.java @@ -16,6 +16,8 @@ package android.graphics; +import android.annotation.UnsupportedAppUsage; + /** * The NinePatch class permits drawing a bitmap in nine or more sections. * Essentially, it allows the creation of custom graphics that will scale the @@ -41,6 +43,7 @@ public class NinePatch { */ public static class InsetStruct { @SuppressWarnings({"UnusedDeclaration"}) // called from JNI + @UnsupportedAppUsage InsetStruct(int opticalLeft, int opticalTop, int opticalRight, int opticalBottom, int outlineLeft, int outlineTop, int outlineRight, int outlineBottom, float outlineRadius, int outlineAlpha, float decodeScale) { @@ -77,6 +80,7 @@ public class NinePatch { } } + @UnsupportedAppUsage private final Bitmap mBitmap; /** @@ -84,6 +88,7 @@ public class NinePatch { * * @hide */ + @UnsupportedAppUsage public long mNativeChunk; private Paint mPaint; @@ -256,7 +261,8 @@ public class NinePatch { * that are transparent. */ public final Region getTransparentRegion(Rect bounds) { - long r = nativeGetTransparentRegion(mBitmap, mNativeChunk, bounds); + long r = nativeGetTransparentRegion(mBitmap.getNativeInstance(), + mNativeChunk, bounds); return r != 0 ? new Region(r) : null; } @@ -277,5 +283,6 @@ public class NinePatch { */ private static native long validateNinePatchChunk(byte[] chunk); private static native void nativeFinalize(long chunk); - private static native long nativeGetTransparentRegion(Bitmap bitmap, long chunk, Rect location); + private static native long nativeGetTransparentRegion(long bitmapHandle, long chunk, + Rect location); } diff --git a/graphics/java/android/graphics/Outline.java b/graphics/java/android/graphics/Outline.java index 1c85df0de590..1fc056c3652f 100644 --- a/graphics/java/android/graphics/Outline.java +++ b/graphics/java/android/graphics/Outline.java @@ -19,6 +19,7 @@ package android.graphics; import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.UnsupportedAppUsage; import android.graphics.drawable.Drawable; import java.lang.annotation.Retention; @@ -66,6 +67,7 @@ public final class Outline { public Path mPath; /** @hide */ + @UnsupportedAppUsage public final Rect mRect = new Rect(); /** @hide */ public float mRadius = RADIUS_UNDEFINED; @@ -271,8 +273,12 @@ public final class Outline { } /** - * Sets the Constructs an Outline from a + * Sets the Outline to a * {@link android.graphics.Path#isConvex() convex path}. + * + * @param convexPath used to construct the Outline. As of + * {@link android.os.Build.VERSION_CODES#Q}, it is no longer required to be + * convex. */ public void setConvexPath(@NonNull Path convexPath) { if (convexPath.isEmpty()) { @@ -280,10 +286,6 @@ public final class Outline { return; } - if (!convexPath.isConvex()) { - throw new IllegalArgumentException("path must be convex"); - } - if (mPath == null) { mPath = new Path(); } diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index 42dac38affba..109d8631284d 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -17,9 +17,16 @@ package android.graphics; import android.annotation.ColorInt; +import android.annotation.ColorLong; +import android.annotation.IntDef; +import android.annotation.IntRange; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.Px; import android.annotation.Size; +import android.annotation.UnsupportedAppUsage; import android.graphics.fonts.FontVariationAxis; +import android.os.Build; import android.os.LocaleList; import android.text.GraphicsOperations; import android.text.SpannableString; @@ -33,6 +40,8 @@ import dalvik.annotation.optimization.FastNative; import libcore.util.NativeAllocationRegistry; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -44,38 +53,39 @@ import java.util.Locale; */ public class Paint { + @UnsupportedAppUsage private long mNativePaint; private long mNativeShader; private long mNativeColorFilter; - // The approximate size of a native paint object. - private static final long NATIVE_PAINT_SIZE = 98; - // Use a Holder to allow static initialization of Paint in the boot image. private static class NoImagePreloadHolder { - public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( - Paint.class.getClassLoader(), nGetNativeFinalizer(), NATIVE_PAINT_SIZE); + public static final NativeAllocationRegistry sRegistry = + NativeAllocationRegistry.createMalloced( + Paint.class.getClassLoader(), nGetNativeFinalizer()); } - private ColorFilter mColorFilter; - private MaskFilter mMaskFilter; - private PathEffect mPathEffect; - private Shader mShader; - private Typeface mTypeface; - private Xfermode mXfermode; + @ColorLong private long mColor; + private ColorFilter mColorFilter; + private MaskFilter mMaskFilter; + private PathEffect mPathEffect; + private Shader mShader; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + private Typeface mTypeface; + private Xfermode mXfermode; - private boolean mHasCompatScaling; - private float mCompatScaling; - private float mInvCompatScaling; + private boolean mHasCompatScaling; + private float mCompatScaling; + private float mInvCompatScaling; - private LocaleList mLocales; - private String mFontFeatureSettings; - private String mFontVariationSettings; + private LocaleList mLocales; + private String mFontFeatureSettings; + private String mFontVariationSettings; - private float mShadowLayerRadius; - private float mShadowLayerDx; - private float mShadowLayerDy; - private int mShadowLayerColor; + private float mShadowLayerRadius; + private float mShadowLayerDx; + private float mShadowLayerDy; + @ColorLong private long mShadowLayerColor; private static final Object sCacheLock = new Object(); @@ -219,7 +229,8 @@ public class Paint { public static final int VERTICAL_TEXT_FLAG = 0x1000; // These flags are always set on a new/reset paint, even if flags 0 is passed. - static final int HIDDEN_DEFAULT_PAINT_FLAGS = DEV_KERN_TEXT_FLAG | EMBEDDED_BITMAP_TEXT_FLAG; + static final int HIDDEN_DEFAULT_PAINT_FLAGS = DEV_KERN_TEXT_FLAG | EMBEDDED_BITMAP_TEXT_FLAG + | FILTER_BITMAP_FLAG; /** * Font hinter option that disables font hinting. @@ -303,38 +314,47 @@ public class Paint { */ public static final int DIRECTION_RTL = 1; + /** @hide */ + @IntDef(prefix = { "CURSOR_" }, value = { + CURSOR_AFTER, CURSOR_AT_OR_AFTER, CURSOR_BEFORE, CURSOR_AT_OR_BEFORE + }) + @Retention(RetentionPolicy.SOURCE) + public @interface CursorOption {} + /** - * Option for getTextRunCursor to compute the valid cursor after - * offset or the limit of the context, whichever is less. - * @hide + * Option for getTextRunCursor. + * + * Compute the valid cursor after offset or the limit of the context, whichever is less. */ public static final int CURSOR_AFTER = 0; /** - * Option for getTextRunCursor to compute the valid cursor at or after - * the offset or the limit of the context, whichever is less. - * @hide + * Option for getTextRunCursor. + * + * Compute the valid cursor at or after the offset or the limit of the context, whichever is + * less. */ public static final int CURSOR_AT_OR_AFTER = 1; /** - * Option for getTextRunCursor to compute the valid cursor before - * offset or the start of the context, whichever is greater. - * @hide + * Option for getTextRunCursor. + * + * Compute the valid cursor before offset or the start of the context, whichever is greater. */ public static final int CURSOR_BEFORE = 2; /** - * Option for getTextRunCursor to compute the valid cursor at or before - * offset or the start of the context, whichever is greater. - * @hide + * Option for getTextRunCursor. + * + * Compute the valid cursor at or before offset or the start of the context, whichever is + * greater. */ public static final int CURSOR_AT_OR_BEFORE = 3; /** - * Option for getTextRunCursor to return offset if the cursor at offset - * is valid, or -1 if it isn't. - * @hide + * Option for getTextRunCursor. + * + * Return offset if the cursor at offset is valid, or -1 if it isn't. */ public static final int CURSOR_AT = 4; @@ -343,19 +363,79 @@ public class Paint { */ private static final int CURSOR_OPT_MAX_VALUE = CURSOR_AT; + /** @hide */ + @IntDef(prefix = { "START_HYPHEN_EDIT_" }, value = { + START_HYPHEN_EDIT_NO_EDIT, + START_HYPHEN_EDIT_INSERT_HYPHEN, + START_HYPHEN_EDIT_INSERT_ZWJ + }) + @Retention(RetentionPolicy.SOURCE) + public @interface StartHyphenEdit {} + /** - * Mask for hyphen edits that happen at the end of a line. Keep in sync with the definition in - * Minikin's Hyphenator.h. - * @hide + * An integer representing the starting of the line has no modification for hyphenation. */ - public static final int HYPHENEDIT_MASK_END_OF_LINE = 0x07; + public static final int START_HYPHEN_EDIT_NO_EDIT = 0x00; /** - * Mask for hyphen edits that happen at the start of a line. Keep in sync with the definition in - * Minikin's Hyphenator.h. - * @hide + * An integer representing the starting of the line has normal hyphen character (U+002D). + */ + public static final int START_HYPHEN_EDIT_INSERT_HYPHEN = 0x01; + + /** + * An integer representing the starting of the line has Zero-Width-Joiner (U+200D). + */ + public static final int START_HYPHEN_EDIT_INSERT_ZWJ = 0x02; + + /** @hide */ + @IntDef(prefix = { "END_HYPHEN_EDIT_" }, value = { + END_HYPHEN_EDIT_NO_EDIT, + END_HYPHEN_EDIT_REPLACE_WITH_HYPHEN, + END_HYPHEN_EDIT_INSERT_HYPHEN, + END_HYPHEN_EDIT_INSERT_ARMENIAN_HYPHEN, + END_HYPHEN_EDIT_INSERT_MAQAF, + END_HYPHEN_EDIT_INSERT_UCAS_HYPHEN, + END_HYPHEN_EDIT_INSERT_ZWJ_AND_HYPHEN + }) + @Retention(RetentionPolicy.SOURCE) + public @interface EndHyphenEdit {} + + /** + * An integer representing the end of the line has no modification for hyphenation. + */ + public static final int END_HYPHEN_EDIT_NO_EDIT = 0x00; + + /** + * An integer representing the character at the end of the line is replaced with hyphen + * character (U+002D). + */ + public static final int END_HYPHEN_EDIT_REPLACE_WITH_HYPHEN = 0x01; + + /** + * An integer representing the end of the line has normal hyphen character (U+002D). + */ + public static final int END_HYPHEN_EDIT_INSERT_HYPHEN = 0x02; + + /** + * An integer representing the end of the line has Armentian hyphen (U+058A). + */ + public static final int END_HYPHEN_EDIT_INSERT_ARMENIAN_HYPHEN = 0x03; + + /** + * An integer representing the end of the line has maqaf (Hebrew hyphen, U+05BE). + */ + public static final int END_HYPHEN_EDIT_INSERT_MAQAF = 0x04; + + /** + * An integer representing the end of the line has Canadian Syllabics hyphen (U+1400). */ - public static final int HYPHENEDIT_MASK_START_OF_LINE = 0x03 << 3; + public static final int END_HYPHEN_EDIT_INSERT_UCAS_HYPHEN = 0x05; + + /** + * An integer representing the end of the line has Zero-Width-Joiner (U+200D) followed by normal + * hyphen character (U+002D). + */ + public static final int END_HYPHEN_EDIT_INSERT_ZWJ_AND_HYPHEN = 0x06; /** * The Style specifies if the primitive being drawn is filled, stroked, or @@ -484,6 +564,7 @@ public class Paint { // ? HINTING_OFF : HINTING_ON); mCompatScaling = mInvCompatScaling = 1; setTextLocales(LocaleList.getAdjustedDefault()); + mColor = Color.pack(Color.BLACK); } /** @@ -509,6 +590,7 @@ public class Paint { // setHinting(DisplayMetrics.DENSITY_DEVICE >= DisplayMetrics.DENSITY_TV // ? HINTING_OFF : HINTING_ON); + mColor = Color.pack(Color.BLACK); mColorFilter = null; mMaskFilter = null; mPathEffect = null; @@ -530,7 +612,7 @@ public class Paint { mShadowLayerRadius = 0.0f; mShadowLayerDx = 0.0f; mShadowLayerDy = 0.0f; - mShadowLayerColor = 0; + mShadowLayerColor = Color.pack(0); } /** @@ -551,6 +633,7 @@ public class Paint { * {@link Paint}. */ private void setClassVariablesFrom(Paint paint) { + mColor = paint.mColor; mColorFilter = paint.mColorFilter; mMaskFilter = paint.mMaskFilter; mPathEffect = paint.mPathEffect; @@ -574,50 +657,8 @@ public class Paint { mShadowLayerColor = paint.mShadowLayerColor; } - /** - * Returns true if all attributes are equal. - * - * The caller is expected to have checked the trivial cases, like the pointers being equal, - * the objects having different classes, or the parameter being null. - * @hide - */ - public boolean hasEqualAttributes(@NonNull Paint other) { - return mColorFilter == other.mColorFilter - && mMaskFilter == other.mMaskFilter - && mPathEffect == other.mPathEffect - && mShader == other.mShader - && mTypeface == other.mTypeface - && mXfermode == other.mXfermode - && mHasCompatScaling == other.mHasCompatScaling - && mCompatScaling == other.mCompatScaling - && mInvCompatScaling == other.mInvCompatScaling - && mBidiFlags == other.mBidiFlags - && mLocales.equals(other.mLocales) - && TextUtils.equals(mFontFeatureSettings, other.mFontFeatureSettings) - && TextUtils.equals(mFontVariationSettings, other.mFontVariationSettings) - && mShadowLayerRadius == other.mShadowLayerRadius - && mShadowLayerDx == other.mShadowLayerDx - && mShadowLayerDy == other.mShadowLayerDy - && mShadowLayerColor == other.mShadowLayerColor - && getFlags() == other.getFlags() - && getHinting() == other.getHinting() - && getStyle() == other.getStyle() - && getColor() == other.getColor() - && getStrokeWidth() == other.getStrokeWidth() - && getStrokeMiter() == other.getStrokeMiter() - && getStrokeCap() == other.getStrokeCap() - && getStrokeJoin() == other.getStrokeJoin() - && getTextAlign() == other.getTextAlign() - && isElegantTextHeight() == other.isElegantTextHeight() - && getTextSize() == other.getTextSize() - && getTextScaleX() == other.getTextScaleX() - && getTextSkewX() == other.getTextSkewX() - && getLetterSpacing() == other.getLetterSpacing() - && getWordSpacing() == other.getWordSpacing() - && getHyphenEdit() == other.getHyphenEdit(); - } - /** @hide */ + @UnsupportedAppUsage public void setCompatibilityScaling(float factor) { if (factor == 1.0) { mHasCompatScaling = false; @@ -635,6 +676,7 @@ public class Paint { * * @hide */ + @UnsupportedAppUsage public long getNativeInstance() { long newNativeShader = mShader == null ? 0 : mShader.getNativeInstance(); if (newNativeShader != mNativeShader) { @@ -800,25 +842,39 @@ public class Paint { * Helper for getFlags(), returning true if UNDERLINE_TEXT_FLAG bit is set * * @return true if the underlineText bit is set in the paint's flags. + * @see #getUnderlinePosition() + * @see #getUnderlineThickness() + * @see #setUnderlineText(boolean) */ public final boolean isUnderlineText() { return (getFlags() & UNDERLINE_TEXT_FLAG) != 0; } /** - * Distance from top of the underline to the baseline. Positive values mean below the baseline. - * This method returns where the underline should be drawn independent of if the underlineText - * bit is set at the moment. - * @hide + * Returns the distance from top of the underline to the baseline in pixels. + * + * The result is positive for positions that are below the baseline. + * This method returns where the underline should be drawn independent of if the {@link + * #UNDERLINE_TEXT_FLAG} bit is set. + * + * @return the position of the underline in pixels + * @see #isUnderlineText() + * @see #getUnderlineThickness() + * @see #setUnderlineText(boolean) */ - public float getUnderlinePosition() { + public @Px float getUnderlinePosition() { return nGetUnderlinePosition(mNativePaint); } /** - * @hide + * Returns the thickness of the underline in pixels. + * + * @return the thickness of the underline in pixels + * @see #isUnderlineText() + * @see #getUnderlinePosition() + * @see #setUnderlineText(boolean) */ - public float getUnderlineThickness() { + public @Px float getUnderlineThickness() { return nGetUnderlineThickness(mNativePaint); } @@ -827,6 +883,9 @@ public class Paint { * * @param underlineText true to set the underlineText bit in the paint's * flags, false to clear it. + * @see #isUnderlineText() + * @see #getUnderlinePosition() + * @see #getUnderlineThickness() */ public void setUnderlineText(boolean underlineText) { nSetUnderlineText(mNativePaint, underlineText); @@ -835,26 +894,40 @@ public class Paint { /** * Helper for getFlags(), returning true if STRIKE_THRU_TEXT_FLAG bit is set * - * @return true if the strikeThruText bit is set in the paint's flags. + * @return true if the {@link #STRIKE_THRU_TEXT_FLAG} bit is set in the paint's flags. + * @see #getStrikeThruPosition() + * @see #getStrikeThruThickness() + * @see #setStrikeThruText(boolean) */ public final boolean isStrikeThruText() { return (getFlags() & STRIKE_THRU_TEXT_FLAG) != 0; } /** - * Distance from top of the strike-through line to the baseline. Negative values mean above the - * baseline. This method returns where the strike-through line should be drawn independent of if - * the strikeThruText bit is set at the moment. - * @hide + * Distance from top of the strike-through line to the baseline in pixels. + * + * The result is negative for positions that are above the baseline. + * This method returns where the strike-through line should be drawn independent of if the + * {@link #STRIKE_THRU_TEXT_FLAG} bit is set. + * + * @return the position of the strike-through line in pixels + * @see #getStrikeThruThickness() + * @see #setStrikeThruText(boolean) + * @see #isStrikeThruText() */ - public float getStrikeThruPosition() { + public @Px float getStrikeThruPosition() { return nGetStrikeThruPosition(mNativePaint); } /** - * @hide + * Returns the thickness of the strike-through line in pixels. + * + * @return the position of the strike-through line in pixels + * @see #getStrikeThruPosition() + * @see #setStrikeThruText(boolean) + * @see #isStrikeThruText() */ - public float getStrikeThruThickness() { + public @Px float getStrikeThruThickness() { return nGetStrikeThruThickness(mNativePaint); } @@ -863,6 +936,9 @@ public class Paint { * * @param strikeThruText true to set the strikeThruText bit in the paint's * flags, false to clear it. + * @see #getStrikeThruPosition() + * @see #getStrikeThruThickness() + * @see #isStrikeThruText() */ public void setStrikeThruText(boolean strikeThruText) { nSetStrikeThruText(mNativePaint, strikeThruText); @@ -935,7 +1011,7 @@ public class Paint { } /** - * Return the paint's color. Note that the color is a 32bit value + * Return the paint's color in sRGB. Note that the color is a 32bit value * containing alpha as well as r,g,b. This 32bit value is not premultiplied, * meaning that its alpha can be any value, regardless of the values of * r,g,b. See the Color class for more details. @@ -944,7 +1020,21 @@ public class Paint { */ @ColorInt public int getColor() { - return nGetColor(mNativePaint); + return Color.toArgb(mColor); + } + + /** + * Return the paint's color. Note that the color is a long with an encoded + * {@link ColorSpace} as well as alpha and r,g,b. These values are not + * premultiplied, meaning that alpha can be any value, regardless of the + * values of r,g,b. See the {@link Color} class for more details. + * + * @return the paint's color, alpha, and {@code ColorSpace} encoded as a + * {@code ColorLong} + */ + @ColorLong + public long getColorLong() { + return mColor; } /** @@ -957,6 +1047,26 @@ public class Paint { */ public void setColor(@ColorInt int color) { nSetColor(mNativePaint, color); + mColor = Color.pack(color); + } + + /** + * Set the paint's color with a {@code ColorLong}. Note that the color is + * a long with an encoded {@link ColorSpace} as well as alpha and r,g,b. + * These values are not premultiplied, meaning that alpha can be any value, + * regardless of the values of r,g,b. See the {@link Color} class for more + * details. + * + * @param color The new color (including alpha and {@link ColorSpace}) + * to set in the paint. + * @throws IllegalArgumentException if the color space encoded in the + * {@code ColorLong} is invalid or unknown. + */ + public void setColor(@ColorLong long color) { + ColorSpace cs = Color.colorSpace(color); + + nSetColor(mNativePaint, cs.getNativeInstance(), color); + mColor = color; } /** @@ -967,7 +1077,7 @@ public class Paint { * @return the alpha component of the paint's color. */ public int getAlpha() { - return nGetAlpha(mNativePaint); + return Math.round(Color.alpha(mColor) * 255.0f); } /** @@ -978,6 +1088,13 @@ public class Paint { * @param a set the alpha component [0..255] of the paint's color. */ public void setAlpha(int a) { + // FIXME: No need to unpack this. Instead, just update the alpha bits. + // b/122959599 + ColorSpace cs = Color.colorSpace(mColor); + float r = Color.red(mColor); + float g = Color.green(mColor); + float b = Color.blue(mColor); + mColor = Color.pack(r, g, b, a * (1.0f / 255), cs); nSetAlpha(mNativePaint, a); } @@ -997,7 +1114,7 @@ public class Paint { * Return the width for stroking. * <p /> * A value of 0 strokes in hairline mode. - * Hairlines always draws a single pixel independent of the canva's matrix. + * Hairlines always draws a single pixel independent of the canvas's matrix. * * @return the paint's stroke width, used whenever the paint's style is * Stroke or StrokeAndFill. @@ -1009,7 +1126,7 @@ public class Paint { /** * Set the width for stroking. * Pass 0 to stroke in hairline mode. - * Hairlines always draws a single pixel independent of the canva's matrix. + * Hairlines always draws a single pixel independent of the canvas's matrix. * * @param width set the paint's stroke width, used whenever the paint's * style is Stroke or StrokeAndFill. @@ -1163,6 +1280,20 @@ public class Paint { } /** + * Get the paint's blend mode object. + * + * @return the paint's blend mode (or null) + */ + @Nullable + public BlendMode getBlendMode() { + if (mXfermode == null) { + return null; + } else { + return BlendMode.fromValue(mXfermode.porterDuffMode); + } + } + + /** * Set or clear the transfer mode object. A transfer mode defines how * source pixels (generate by a drawing command) are composited with * the destination pixels (content of the render target). @@ -1176,6 +1307,11 @@ public class Paint { * @return xfermode */ public Xfermode setXfermode(Xfermode xfermode) { + return installXfermode(xfermode); + } + + @Nullable + private Xfermode installXfermode(Xfermode xfermode) { int newMode = xfermode != null ? xfermode.porterDuffMode : Xfermode.DEFAULT; int curMode = mXfermode != null ? mXfermode.porterDuffMode : Xfermode.DEFAULT; if (newMode != curMode) { @@ -1186,6 +1322,22 @@ public class Paint { } /** + * Set or clear the blend mode. A blend mode defines how source pixels + * (generated by a drawing command) are composited with the destination pixels + * (content of the render target). + * <p /> + * Pass null to clear any previous blend mode. + * <p /> + * + * @see BlendMode + * + * @param blendmode May be null. The blend mode to be installed in the paint + */ + public void setBlendMode(@Nullable BlendMode blendmode) { + installXfermode(blendmode != null ? blendmode.getXfermode() : null); + } + + /** * Get the paint's patheffect object. * * @return the paint's patheffect (or null) @@ -1315,12 +1467,33 @@ public class Paint { * The alpha of the shadow will be the paint's alpha if the shadow color is * opaque, or the alpha from the shadow color if not. */ - public void setShadowLayer(float radius, float dx, float dy, int shadowColor) { - mShadowLayerRadius = radius; - mShadowLayerDx = dx; - mShadowLayerDy = dy; - mShadowLayerColor = shadowColor; - nSetShadowLayer(mNativePaint, radius, dx, dy, shadowColor); + public void setShadowLayer(float radius, float dx, float dy, @ColorInt int shadowColor) { + setShadowLayer(radius, dx, dy, Color.pack(shadowColor)); + } + + /** + * This draws a shadow layer below the main layer, with the specified + * offset and color, and blur radius. If radius is 0, then the shadow + * layer is removed. + * <p> + * Can be used to create a blurred shadow underneath text. Support for use + * with other drawing operations is constrained to the software rendering + * pipeline. + * <p> + * The alpha of the shadow will be the paint's alpha if the shadow color is + * opaque, or the alpha from the shadow color if not. + * + * @throws IllegalArgumentException if the color space encoded in the + * {@code ColorLong} is invalid or unknown. + */ + public void setShadowLayer(float radius, float dx, float dy, @ColorLong long shadowColor) { + ColorSpace cs = Color.colorSpace(shadowColor); + nSetShadowLayer(mNativePaint, radius, dx, dy, cs.getNativeInstance(), shadowColor); + + mShadowLayerRadius = radius; + mShadowLayerDx = dx; + mShadowLayerDy = dy; + mShadowLayerColor = shadowColor; } /** @@ -1341,6 +1514,54 @@ public class Paint { } /** + * Returns the blur radius of the shadow layer. + * @see #setShadowLayer(float,float,float,int) + * @see #setShadowLayer(float,float,float,long) + */ + public float getShadowLayerRadius() { + return mShadowLayerRadius; + } + + /** + * Returns the x offset of the shadow layer. + * @see #setShadowLayer(float,float,float,int) + * @see #setShadowLayer(float,float,float,long) + */ + public float getShadowLayerDx() { + return mShadowLayerDx; + } + + /** + * Returns the y offset of the shadow layer. + * @see #setShadowLayer(float,float,float,int) + * @see #setShadowLayer(float,float,float,long) + */ + public float getShadowLayerDy() { + return mShadowLayerDy; + } + + /** + * Returns the color of the shadow layer. + * @see #setShadowLayer(float,float,float,int) + * @see #setShadowLayer(float,float,float,long) + */ + public @ColorInt int getShadowLayerColor() { + return Color.toArgb(mShadowLayerColor); + } + + /** + * Returns the color of the shadow layer. + * + * @return the shadow layer's color encoded as a {@link ColorLong}. + * @see #setShadowLayer(float,float,float,int) + * @see #setShadowLayer(float,float,float,long) + * @see Color + */ + public @ColorLong long getShadowLayerColorLong() { + return mShadowLayerColor; + } + + /** * Return the paint's Align value for drawing text. This controls how the * text is positioned relative to its origin. LEFT align means that all of * the text will be drawn to the right of its origin (i.e. the origin @@ -1555,24 +1776,27 @@ public class Paint { } /** - * Return the paint's word-spacing for text. The default value is 0. + * Return the paint's extra word-spacing for text. * - * @return the paint's word-spacing for drawing text. - * @hide + * The default value is 0. + * + * @return the paint's extra word-spacing for drawing text in pixels. + * @see #setWordSpacing(float) */ - public float getWordSpacing() { + public @Px float getWordSpacing() { return nGetWordSpacing(mNativePaint); } /** - * Set the paint's word-spacing for text. The default value is 0. - * The value is in pixels (note the units are not the same as for - * letter-spacing). + * Set the paint's extra word-spacing for text. * - * @param wordSpacing set the paint's word-spacing for drawing text. - * @hide + * Increases the white space width between words with the given amount of pixels. + * The default value is 0. + * + * @param wordSpacing set the paint's extra word-spacing for drawing text in pixels. + * @see #getWordSpacing() */ - public void setWordSpacing(float wordSpacing) { + public void setWordSpacing(@Px float wordSpacing) { nSetWordSpacing(mNativePaint, wordSpacing); } @@ -1699,27 +1923,80 @@ public class Paint { } /** - * Get the current value of hyphen edit. + * Get the current value of start hyphen edit. * - * @return the current hyphen edit value + * The default value is 0 which is equivalent to {@link #START_HYPHEN_EDIT_NO_EDIT}. * - * @hide + * @return the current starting hyphen edit value + * @see #setStartHyphenEdit(int) */ - public int getHyphenEdit() { - return nGetHyphenEdit(mNativePaint); + public @StartHyphenEdit int getStartHyphenEdit() { + return nGetStartHyphenEdit(mNativePaint); } /** - * Set a hyphen edit on the paint (causes a hyphen to be added to text when - * measured or drawn). + * Get the current value of end hyphen edit. * - * @param hyphen 0 for no edit, 1 for adding a hyphen at the end, etc. - * Definition of various values are in the HyphenEdit class in Minikin's Hyphenator.h. + * The default value is 0 which is equivalent to {@link #END_HYPHEN_EDIT_NO_EDIT}. * - * @hide + * @return the current starting hyphen edit value + * @see #setStartHyphenEdit(int) + */ + public @EndHyphenEdit int getEndHyphenEdit() { + return nGetEndHyphenEdit(mNativePaint); + } + + /** + * Set a start hyphen edit on the paint. + * + * By setting start hyphen edit, the measurement and drawing is performed with modifying + * hyphenation at the start of line. For example, by passing + * {@link #START_HYPHEN_EDIT_INSERT_HYPHEN} like as follows, HYPHEN(U+2010) + * character is appended at the start of line. + * + * <pre> + * <code> + * Paint paint = new Paint(); + * paint.setStartHyphenEdit(Paint.START_HYPHEN_EDIT_INSERT_HYPHEN); + * paint.measureText("abc", 0, 3); // Returns the width of "-abc" + * Canvas.drawText("abc", 0, 3, 0f, 0f, paint); // Draws "-abc" + * </code> + * </pre> + * + * The default value is 0 which is equivalent to + * {@link #START_HYPHEN_EDIT_NO_EDIT}. + * + * @param startHyphen a start hyphen edit value. + * @see #getStartHyphenEdit() + */ + public void setStartHyphenEdit(@StartHyphenEdit int startHyphen) { + nSetStartHyphenEdit(mNativePaint, startHyphen); + } + + /** + * Set a end hyphen edit on the paint. + * + * By setting end hyphen edit, the measurement and drawing is performed with modifying + * hyphenation at the end of line. For example, by passing + * {@link #END_HYPHEN_EDIT_INSERT_HYPHEN} like as follows, HYPHEN(U+2010) + * character is appended at the end of line. + * + * <pre> + * <code> + * Paint paint = new Paint(); + * paint.setEndHyphenEdit(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN); + * paint.measureText("abc", 0, 3); // Returns the width of "abc-" + * Canvas.drawText("abc", 0, 3, 0f, 0f, paint); // Draws "abc-" + * </code> + * </pre> + * + * The default value is 0 which is equivalent to {@link #END_HYPHEN_EDIT_NO_EDIT}. + * + * @param endHyphen a end hyphen edit value. + * @see #getEndHyphenEdit() */ - public void setHyphenEdit(int hyphen) { - nSetHyphenEdit(mNativePaint, hyphen); + public void setEndHyphenEdit(@EndHyphenEdit int endHyphen) { + nSetEndHyphenEdit(mNativePaint, endHyphen); } /** @@ -2255,16 +2532,53 @@ public class Paint { } /** - * Convenience overload that takes a char array instead of a - * String. + * Retrieve the character advances of the text. * - * @see #getTextRunAdvances(String, int, int, int, int, boolean, float[], int) - * @hide - */ - public float getTextRunAdvances(char[] chars, int index, int count, - int contextIndex, int contextCount, boolean isRtl, float[] advances, - int advancesIndex) { - + * Returns the total advance width for the characters in the run from {@code index} for + * {@code count} of chars, and if {@code advances} is not null, the advance assigned to each of + * these characters (java chars). + * + * <p> + * The trailing surrogate in a valid surrogate pair is assigned an advance of 0. Thus the + * number of returned advances is always equal to count, not to the number of unicode codepoints + * represented by the run. + * </p> + * + * <p> + * In the case of conjuncts or combining marks, the total advance is assigned to the first + * logical character, and the following characters are assigned an advance of 0. + * </p> + * + * <p> + * This generates the sum of the advances of glyphs for characters in a reordered cluster as the + * width of the first logical character in the cluster, and 0 for the widths of all other + * characters in the cluster. In effect, such clusters are treated like conjuncts. + * </p> + * + * <p> + * The shaping bounds limit the amount of context available outside start and end that can be + * used for shaping analysis. These bounds typically reflect changes in bidi level or font + * metrics across which shaping does not occur. + * </p> + * + * @param chars the text to measure. + * @param index the index of the first character to measure + * @param count the number of characters to measure + * @param contextIndex the index of the first character to use for shaping context. + * Context must cover the measureing target. + * @param contextCount the number of character to use for shaping context. + * Context must cover the measureing target. + * @param isRtl whether the run is in RTL direction + * @param advances array to receive the advances, must have room for all advances. + * This can be null if only total advance is needed + * @param advancesIndex the position in advances at which to put the advance corresponding to + * the character at start + * @return the total advance in pixels + */ + public float getTextRunAdvances(@NonNull char[] chars, @IntRange(from = 0) int index, + @IntRange(from = 0) int count, @IntRange(from = 0) int contextIndex, + @IntRange(from = 0) int contextCount, boolean isRtl, @Nullable float[] advances, + @IntRange(from = 0) int advancesIndex) { if (chars == null) { throw new IllegalArgumentException("text cannot be null"); } @@ -2301,158 +2615,32 @@ public class Paint { } /** - * Convenience overload that takes a CharSequence instead of a - * String. - * - * @see #getTextRunAdvances(String, int, int, int, int, boolean, float[], int) - * @hide - */ - public float getTextRunAdvances(CharSequence text, int start, int end, - int contextStart, int contextEnd, boolean isRtl, float[] advances, - int advancesIndex) { - if (text == null) { - throw new IllegalArgumentException("text cannot be null"); - } - if ((start | end | contextStart | contextEnd | advancesIndex | (end - start) - | (start - contextStart) | (contextEnd - end) - | (text.length() - contextEnd) - | (advances == null ? 0 : - (advances.length - advancesIndex - (end - start)))) < 0) { - throw new IndexOutOfBoundsException(); - } - - if (text instanceof String) { - return getTextRunAdvances((String) text, start, end, - contextStart, contextEnd, isRtl, advances, advancesIndex); - } - if (text instanceof SpannedString || - text instanceof SpannableString) { - return getTextRunAdvances(text.toString(), start, end, - contextStart, contextEnd, isRtl, advances, advancesIndex); - } - if (text instanceof GraphicsOperations) { - return ((GraphicsOperations) text).getTextRunAdvances(start, end, - contextStart, contextEnd, isRtl, advances, advancesIndex, this); - } - if (text.length() == 0 || end == start) { - return 0f; - } - - int contextLen = contextEnd - contextStart; - int len = end - start; - char[] buf = TemporaryBuffer.obtain(contextLen); - TextUtils.getChars(text, contextStart, contextEnd, buf, 0); - float result = getTextRunAdvances(buf, start - contextStart, len, - 0, contextLen, isRtl, advances, advancesIndex); - TemporaryBuffer.recycle(buf); - return result; - } - - /** - * Returns the total advance width for the characters in the run - * between start and end, and if advances is not null, the advance - * assigned to each of these characters (java chars). - * - * <p>The trailing surrogate in a valid surrogate pair is assigned - * an advance of 0. Thus the number of returned advances is - * always equal to count, not to the number of unicode codepoints - * represented by the run. + * Returns the next cursor position in the run. * - * <p>In the case of conjuncts or combining marks, the total - * advance is assigned to the first logical character, and the - * following characters are assigned an advance of 0. - * - * <p>This generates the sum of the advances of glyphs for - * characters in a reordered cluster as the width of the first - * logical character in the cluster, and 0 for the widths of all - * other characters in the cluster. In effect, such clusters are - * treated like conjuncts. - * - * <p>The shaping bounds limit the amount of context available - * outside start and end that can be used for shaping analysis. - * These bounds typically reflect changes in bidi level or font - * metrics across which shaping does not occur. - * - * @param text the text to measure. Cannot be null. - * @param start the index of the first character to measure - * @param end the index past the last character to measure - * @param contextStart the index of the first character to use for shaping context, - * must be <= start - * @param contextEnd the index past the last character to use for shaping context, - * must be >= end - * @param isRtl whether the run is in RTL direction - * @param advances array to receive the advances, must have room for all advances, - * can be null if only total advance is needed - * @param advancesIndex the position in advances at which to put the - * advance corresponding to the character at start - * @return the total advance - * - * @hide - */ - public float getTextRunAdvances(String text, int start, int end, int contextStart, - int contextEnd, boolean isRtl, float[] advances, int advancesIndex) { - if (text == null) { - throw new IllegalArgumentException("text cannot be null"); - } - if ((start | end | contextStart | contextEnd | advancesIndex | (end - start) - | (start - contextStart) | (contextEnd - end) - | (text.length() - contextEnd) - | (advances == null ? 0 : - (advances.length - advancesIndex - (end - start)))) < 0) { - throw new IndexOutOfBoundsException(); - } - - if (text.length() == 0 || start == end) { - return 0f; - } - - if (!mHasCompatScaling) { - return nGetTextAdvances(mNativePaint, text, start, end, contextStart, contextEnd, - isRtl ? BIDI_FORCE_RTL : BIDI_FORCE_LTR, advances, advancesIndex); - } - - final float oldSize = getTextSize(); - setTextSize(oldSize * mCompatScaling); - final float totalAdvance = nGetTextAdvances(mNativePaint, text, start, end, contextStart, - contextEnd, isRtl ? BIDI_FORCE_RTL : BIDI_FORCE_LTR, advances, advancesIndex); - setTextSize(oldSize); - - if (advances != null) { - for (int i = advancesIndex, e = i + (end - start); i < e; i++) { - advances[i] *= mInvCompatScaling; - } - } - return totalAdvance * mInvCompatScaling; // assume errors are insignificant - } - - /** - * Returns the next cursor position in the run. This avoids placing the - * cursor between surrogates, between characters that form conjuncts, - * between base characters and combining marks, or within a reordering - * cluster. + * This avoids placing the cursor between surrogates, between characters that form conjuncts, + * between base characters and combining marks, or within a reordering cluster. * - * <p>ContextStart and offset are relative to the start of text. - * The context is the shaping context for cursor movement, generally - * the bounds of the metric span enclosing the cursor in the direction of - * movement. + * <p> + * ContextStart and offset are relative to the start of text. + * The context is the shaping context for cursor movement, generally the bounds of the metric + * span enclosing the cursor in the direction of movement. * - * <p>If cursorOpt is {@link #CURSOR_AT} and the offset is not a valid - * cursor position, this returns -1. Otherwise this will never return a - * value before contextStart or after contextStart + contextLength. + * <p> + * If cursorOpt is {@link #CURSOR_AT} and the offset is not a valid cursor position, this + * returns -1. Otherwise this will never return a value before contextStart or after + * contextStart + contextLength. * * @param text the text * @param contextStart the start of the context * @param contextLength the length of the context - * @param dir either {@link #DIRECTION_RTL} or {@link #DIRECTION_LTR} + * @param isRtl true if the paragraph context is RTL, otherwise false * @param offset the cursor position to move from - * @param cursorOpt how to move the cursor, one of {@link #CURSOR_AFTER}, - * {@link #CURSOR_AT_OR_AFTER}, {@link #CURSOR_BEFORE}, - * {@link #CURSOR_AT_OR_BEFORE}, or {@link #CURSOR_AT} + * @param cursorOpt how to move the cursor * @return the offset of the next position, or -1 - * @hide */ - public int getTextRunCursor(char[] text, int contextStart, int contextLength, - int dir, int offset, int cursorOpt) { + public int getTextRunCursor(@NonNull char[] text, @IntRange(from = 0) int contextStart, + @IntRange(from = 0) int contextLength, boolean isRtl, @IntRange(from = 0) int offset, + @CursorOption int cursorOpt) { int contextEnd = contextStart + contextLength; if (((contextStart | contextEnd | offset | (contextEnd - contextStart) | (offset - contextStart) | (contextEnd - offset) @@ -2461,85 +2649,87 @@ public class Paint { throw new IndexOutOfBoundsException(); } - return nGetTextRunCursor(mNativePaint, text, contextStart, contextLength, dir, offset, - cursorOpt); + return nGetTextRunCursor(mNativePaint, text, contextStart, contextLength, + isRtl ? DIRECTION_RTL : DIRECTION_LTR, offset, cursorOpt); } /** - * Returns the next cursor position in the run. This avoids placing the - * cursor between surrogates, between characters that form conjuncts, - * between base characters and combining marks, or within a reordering - * cluster. + * Returns the next cursor position in the run. + * + * This avoids placing the cursor between surrogates, between characters that form conjuncts, + * between base characters and combining marks, or within a reordering cluster. * - * <p>ContextStart, contextEnd, and offset are relative to the start of + * <p> + * ContextStart, contextEnd, and offset are relative to the start of * text. The context is the shaping context for cursor movement, generally * the bounds of the metric span enclosing the cursor in the direction of * movement. * - * <p>If cursorOpt is {@link #CURSOR_AT} and the offset is not a valid - * cursor position, this returns -1. Otherwise this will never return a - * value before contextStart or after contextEnd. + * <p> + * If cursorOpt is {@link #CURSOR_AT} and the offset is not a valid cursor position, this + * returns -1. Otherwise this will never return a value before contextStart or after + * contextEnd. * * @param text the text * @param contextStart the start of the context * @param contextEnd the end of the context - * @param dir either {@link #DIRECTION_RTL} or {@link #DIRECTION_LTR} + * @param isRtl true if the paragraph context is RTL, otherwise false * @param offset the cursor position to move from - * @param cursorOpt how to move the cursor, one of {@link #CURSOR_AFTER}, - * {@link #CURSOR_AT_OR_AFTER}, {@link #CURSOR_BEFORE}, - * {@link #CURSOR_AT_OR_BEFORE}, or {@link #CURSOR_AT} + * @param cursorOpt how to move the cursor * @return the offset of the next position, or -1 - * @hide */ - public int getTextRunCursor(CharSequence text, int contextStart, - int contextEnd, int dir, int offset, int cursorOpt) { + public int getTextRunCursor(@NonNull CharSequence text, @IntRange(from = 0) int contextStart, + @IntRange(from = 0) int contextEnd, boolean isRtl, @IntRange(from = 0) int offset, + @CursorOption int cursorOpt) { if (text instanceof String || text instanceof SpannedString || text instanceof SpannableString) { return getTextRunCursor(text.toString(), contextStart, contextEnd, - dir, offset, cursorOpt); + isRtl, offset, cursorOpt); } if (text instanceof GraphicsOperations) { return ((GraphicsOperations) text).getTextRunCursor( - contextStart, contextEnd, dir, offset, cursorOpt, this); + contextStart, contextEnd, isRtl, offset, cursorOpt, this); } int contextLen = contextEnd - contextStart; char[] buf = TemporaryBuffer.obtain(contextLen); TextUtils.getChars(text, contextStart, contextEnd, buf, 0); - int relPos = getTextRunCursor(buf, 0, contextLen, dir, offset - contextStart, cursorOpt); + int relPos = getTextRunCursor(buf, 0, contextLen, isRtl, offset - contextStart, cursorOpt); TemporaryBuffer.recycle(buf); return (relPos == -1) ? -1 : relPos + contextStart; } /** - * Returns the next cursor position in the run. This avoids placing the - * cursor between surrogates, between characters that form conjuncts, - * between base characters and combining marks, or within a reordering - * cluster. + * Returns the next cursor position in the run. * - * <p>ContextStart, contextEnd, and offset are relative to the start of - * text. The context is the shaping context for cursor movement, generally - * the bounds of the metric span enclosing the cursor in the direction of - * movement. + * This avoids placing the cursor between surrogates, between characters that form conjuncts, + * between base characters and combining marks, or within a reordering cluster. + * + * <p> + * ContextStart, contextEnd, and offset are relative to the start of text. The context is the + * shaping context for cursor movement, generally the bounds of the metric span enclosing the + * cursor in the direction of movement. + * </p> * - * <p>If cursorOpt is {@link #CURSOR_AT} and the offset is not a valid - * cursor position, this returns -1. Otherwise this will never return a - * value before contextStart or after contextEnd. + * <p> + * If cursorOpt is {@link #CURSOR_AT} and the offset is not a valid cursor position, this + * returns -1. Otherwise this will never return a value before contextStart or after + * contextEnd. + * </p> * * @param text the text * @param contextStart the start of the context * @param contextEnd the end of the context - * @param dir either {@link #DIRECTION_RTL} or {@link #DIRECTION_LTR} + * @param isRtl true if the paragraph context is RTL, otherwise false. * @param offset the cursor position to move from - * @param cursorOpt how to move the cursor, one of {@link #CURSOR_AFTER}, - * {@link #CURSOR_AT_OR_AFTER}, {@link #CURSOR_BEFORE}, - * {@link #CURSOR_AT_OR_BEFORE}, or {@link #CURSOR_AT} + * @param cursorOpt how to move the cursor * @return the offset of the next position, or -1 * @hide */ - public int getTextRunCursor(String text, int contextStart, int contextEnd, - int dir, int offset, int cursorOpt) { + public int getTextRunCursor(@NonNull String text, @IntRange(from = 0) int contextStart, + @IntRange(from = 0) int contextEnd, boolean isRtl, @IntRange(from = 0) int offset, + @CursorOption int cursorOpt) { if (((contextStart | contextEnd | offset | (contextEnd - contextStart) | (offset - contextStart) | (contextEnd - offset) | (text.length() - contextEnd) | cursorOpt) < 0) @@ -2547,8 +2737,8 @@ public class Paint { throw new IndexOutOfBoundsException(); } - return nGetTextRunCursor(mNativePaint, text, contextStart, contextEnd, dir, offset, - cursorOpt); + return nGetTextRunCursor(mNativePaint, text, contextStart, contextEnd, + isRtl ? DIRECTION_RTL : DIRECTION_LTR, offset, cursorOpt); } /** @@ -2592,6 +2782,8 @@ public class Paint { } /** + * Retrieve the text boundary box and store to bounds. + * * Return in bounds (allocated by the caller) the smallest rectangle that * encloses all of the characters, with an implied origin at (0,0). * @@ -2611,16 +2803,21 @@ public class Paint { } /** + * Retrieve the text boundary box and store to bounds. + * * Return in bounds (allocated by the caller) the smallest rectangle that * encloses all of the characters, with an implied origin at (0,0). * + * Note that styles are ignored even if you pass {@link android.text.Spanned} instance. + * Use {@link android.text.StaticLayout} for measuring bounds of {@link android.text.Spanned}. + * * @param text text to measure and return its bounds * @param start index of the first char in the text to measure * @param end 1 past the last char in the text to measure * @param bounds returns the unioned bounds of all the text. Must be allocated by the caller - * @hide */ - public void getTextBounds(CharSequence text, int start, int end, Rect bounds) { + public void getTextBounds(@NonNull CharSequence text, int start, int end, + @NonNull Rect bounds) { if ((start | end | (end - start) | (text.length() - end)) < 0) { throw new IndexOutOfBoundsException(); } @@ -2929,7 +3126,8 @@ public class Paint { int mMinikinLocaleListId); @CriticalNative private static native void nSetShadowLayer(long paintPtr, - float radius, float dx, float dy, int color); + float radius, float dx, float dy, long colorSpaceHandle, + @ColorLong long shadowColor); @CriticalNative private static native boolean nHasShadowLayer(long paintPtr); @CriticalNative @@ -2941,9 +3139,13 @@ public class Paint { @CriticalNative private static native void nSetWordSpacing(long paintPtr, float wordSpacing); @CriticalNative - private static native int nGetHyphenEdit(long paintPtr); + private static native int nGetStartHyphenEdit(long paintPtr); + @CriticalNative + private static native int nGetEndHyphenEdit(long paintPtr); @CriticalNative - private static native void nSetHyphenEdit(long paintPtr, int hyphen); + private static native void nSetStartHyphenEdit(long paintPtr, int hyphen); + @CriticalNative + private static native void nSetEndHyphenEdit(long paintPtr, int hyphen); @CriticalNative private static native void nSetStrokeMiter(long paintPtr, float miter); @CriticalNative @@ -2977,12 +3179,11 @@ public class Paint { @CriticalNative private static native void nSetFilterBitmap(long paintPtr, boolean filter); @CriticalNative - private static native int nGetColor(long paintPtr); + private static native void nSetColor(long paintPtr, long colorSpaceHandle, + @ColorLong long color); @CriticalNative private static native void nSetColor(long paintPtr, @ColorInt int color); @CriticalNative - private static native int nGetAlpha(long paintPtr); - @CriticalNative private static native void nSetStrikeThruText(long paintPtr, boolean strikeThruText); @CriticalNative private static native boolean nIsElegantTextHeight(long paintPtr); diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java index cd0862cd13fe..7282d52d6e23 100644 --- a/graphics/java/android/graphics/Path.java +++ b/graphics/java/android/graphics/Path.java @@ -20,6 +20,7 @@ import android.annotation.FloatRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Size; +import android.annotation.UnsupportedAppUsage; import dalvik.annotation.optimization.CriticalNative; import dalvik.annotation.optimization.FastNative; @@ -35,8 +36,9 @@ import libcore.util.NativeAllocationRegistry; */ public class Path { - private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( - Path.class.getClassLoader(), nGetFinalizer(), 48 /* dummy size */); + private static final NativeAllocationRegistry sRegistry = + NativeAllocationRegistry.createMalloced( + Path.class.getClassLoader(), nGetFinalizer()); /** * @hide @@ -46,10 +48,12 @@ public class Path { /** * @hide */ + @UnsupportedAppUsage public boolean isSimplePath = true; /** * @hide */ + @UnsupportedAppUsage public Region rects; private Direction mLastDirection = null; @@ -66,7 +70,7 @@ public class Path { * * @param src The path to copy from when initializing the new path */ - public Path(Path src) { + public Path(@Nullable Path src) { long valNative = 0; if (src != null) { valNative = src.mNativePath; @@ -168,7 +172,7 @@ public class Path { * @see Op * @see #op(Path, Path, android.graphics.Path.Op) */ - public boolean op(Path path, Op op) { + public boolean op(@NonNull Path path, @NonNull Op op) { return op(this, path, op); } @@ -186,7 +190,7 @@ public class Path { * @see Op * @see #op(Path, android.graphics.Path.Op) */ - public boolean op(Path path1, Path path2, Op op) { + public boolean op(@NonNull Path path1, @NonNull Path path2, @NonNull Op op) { if (nOp(path1.mNativePath, path2.mNativePath, op.ordinal(), this.mNativePath)) { isSimplePath = false; rects = null; @@ -255,6 +259,7 @@ public class Path { * * @return the path's fill type */ + @NonNull public FillType getFillType() { return sFillTypeArray[nGetFillType(mNativePath)]; } @@ -264,7 +269,7 @@ public class Path { * * @param ft The new fill type for this path */ - public void setFillType(FillType ft) { + public void setFillType(@NonNull FillType ft) { nSetFillType(mNativePath, ft.nativeInt); } @@ -318,7 +323,7 @@ public class Path { * @param exact This parameter is no longer used. */ @SuppressWarnings({"UnusedDeclaration"}) - public void computeBounds(RectF bounds, boolean exact) { + public void computeBounds(@NonNull RectF bounds, boolean exact) { nComputeBounds(mNativePath, bounds); } @@ -461,7 +466,7 @@ public class Path { * mod 360. * @param forceMoveTo If true, always begin a new contour with the arc */ - public void arcTo(RectF oval, float startAngle, float sweepAngle, + public void arcTo(@NonNull RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo) { arcTo(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle, forceMoveTo); } @@ -477,7 +482,7 @@ public class Path { * @param startAngle Starting angle (in degrees) where the arc begins * @param sweepAngle Sweep angle (in degrees) measured clockwise */ - public void arcTo(RectF oval, float startAngle, float sweepAngle) { + public void arcTo(@NonNull RectF oval, float startAngle, float sweepAngle) { arcTo(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle, false); } @@ -542,7 +547,7 @@ public class Path { * @param rect The rectangle to add as a closed contour to the path * @param dir The direction to wind the rectangle's contour */ - public void addRect(RectF rect, Direction dir) { + public void addRect(@NonNull RectF rect, @NonNull Direction dir) { addRect(rect.left, rect.top, rect.right, rect.bottom, dir); } @@ -555,7 +560,7 @@ public class Path { * @param bottom The bottom of a rectangle to add to the path * @param dir The direction to wind the rectangle's contour */ - public void addRect(float left, float top, float right, float bottom, Direction dir) { + public void addRect(float left, float top, float right, float bottom, @NonNull Direction dir) { detectSimplePath(left, top, right, bottom, dir); nAddRect(mNativePath, left, top, right, bottom, dir.nativeInt); } @@ -566,7 +571,7 @@ public class Path { * @param oval The bounds of the oval to add as a closed contour to the path * @param dir The direction to wind the oval's contour */ - public void addOval(RectF oval, Direction dir) { + public void addOval(@NonNull RectF oval, @NonNull Direction dir) { addOval(oval.left, oval.top, oval.right, oval.bottom, dir); } @@ -575,7 +580,7 @@ public class Path { * * @param dir The direction to wind the oval's contour */ - public void addOval(float left, float top, float right, float bottom, Direction dir) { + public void addOval(float left, float top, float right, float bottom, @NonNull Direction dir) { isSimplePath = false; nAddOval(mNativePath, left, top, right, bottom, dir.nativeInt); } @@ -588,7 +593,7 @@ public class Path { * @param radius The radius of a circle to add to the path * @param dir The direction to wind the circle's contour */ - public void addCircle(float x, float y, float radius, Direction dir) { + public void addCircle(float x, float y, float radius, @NonNull Direction dir) { isSimplePath = false; nAddCircle(mNativePath, x, y, radius, dir.nativeInt); } @@ -600,7 +605,7 @@ public class Path { * @param startAngle Starting angle (in degrees) where the arc begins * @param sweepAngle Sweep angle (in degrees) measured clockwise */ - public void addArc(RectF oval, float startAngle, float sweepAngle) { + public void addArc(@NonNull RectF oval, float startAngle, float sweepAngle) { addArc(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle); } @@ -624,7 +629,7 @@ public class Path { * @param ry The y-radius of the rounded corners on the round-rectangle * @param dir The direction to wind the round-rectangle's contour */ - public void addRoundRect(RectF rect, float rx, float ry, Direction dir) { + public void addRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Direction dir) { addRoundRect(rect.left, rect.top, rect.right, rect.bottom, rx, ry, dir); } @@ -636,7 +641,7 @@ public class Path { * @param dir The direction to wind the round-rectangle's contour */ public void addRoundRect(float left, float top, float right, float bottom, float rx, float ry, - Direction dir) { + @NonNull Direction dir) { isSimplePath = false; nAddRoundRect(mNativePath, left, top, right, bottom, rx, ry, dir.nativeInt); } @@ -650,7 +655,7 @@ public class Path { * @param radii Array of 8 values, 4 pairs of [X,Y] radii * @param dir The direction to wind the round-rectangle's contour */ - public void addRoundRect(RectF rect, float[] radii, Direction dir) { + public void addRoundRect(@NonNull RectF rect, @NonNull float[] radii, @NonNull Direction dir) { if (rect == null) { throw new NullPointerException("need rect parameter"); } @@ -665,8 +670,8 @@ public class Path { * @param radii Array of 8 values, 4 pairs of [X,Y] radii * @param dir The direction to wind the round-rectangle's contour */ - public void addRoundRect(float left, float top, float right, float bottom, float[] radii, - Direction dir) { + public void addRoundRect(float left, float top, float right, float bottom, + @NonNull float[] radii, @NonNull Direction dir) { if (radii.length < 8) { throw new ArrayIndexOutOfBoundsException("radii[] needs 8 values"); } @@ -680,7 +685,7 @@ public class Path { * @param src The path to add as a new contour * @param dx The amount to translate the path in X as it is added */ - public void addPath(Path src, float dx, float dy) { + public void addPath(@NonNull Path src, float dx, float dy) { isSimplePath = false; nAddPath(mNativePath, src.mNativePath, dx, dy); } @@ -690,7 +695,7 @@ public class Path { * * @param src The path that is appended to the current path */ - public void addPath(Path src) { + public void addPath(@NonNull Path src) { isSimplePath = false; nAddPath(mNativePath, src.mNativePath); } @@ -700,7 +705,7 @@ public class Path { * * @param src The path to add as a new contour */ - public void addPath(Path src, Matrix matrix) { + public void addPath(@NonNull Path src, @NonNull Matrix matrix) { if (!src.isSimplePath) isSimplePath = false; nAddPath(mNativePath, src.mNativePath, matrix.native_instance); } @@ -760,7 +765,7 @@ public class Path { * @param dst The transformed path is written here. If dst is null, * then the the original path is modified */ - public void transform(Matrix matrix, Path dst) { + public void transform(@NonNull Matrix matrix, @Nullable Path dst) { long dstNative = 0; if (dst != null) { dst.isSimplePath = false; @@ -774,7 +779,7 @@ public class Path { * * @param matrix The matrix to apply to the path */ - public void transform(Matrix matrix) { + public void transform(@NonNull Matrix matrix) { isSimplePath = false; nTransform(mNativePath, matrix.native_instance); } diff --git a/graphics/java/android/graphics/Picture.java b/graphics/java/android/graphics/Picture.java index ac386979ff5b..8d12cbffc793 100644 --- a/graphics/java/android/graphics/Picture.java +++ b/graphics/java/android/graphics/Picture.java @@ -16,6 +16,9 @@ package android.graphics; +import android.annotation.NonNull; +import android.annotation.UnsupportedAppUsage; + import java.io.InputStream; import java.io.OutputStream; @@ -32,6 +35,8 @@ import java.io.OutputStream; */ public class Picture { private PictureCanvas mRecordingCanvas; + // TODO: Figure out if this was a false-positive + @UnsupportedAppUsage(maxTargetSdk = 28) private long mNativePicture; private boolean mRequiresHwAcceleration; @@ -53,23 +58,43 @@ public class Picture { this(nativeConstructor(src != null ? src.mNativePicture : 0)); } - private Picture(long nativePicture) { + /** @hide */ + public Picture(long nativePicture) { if (nativePicture == 0) { - throw new RuntimeException(); + throw new IllegalArgumentException(); } mNativePicture = nativePicture; } + /** + * Immediately releases the backing data of the Picture. This object will no longer + * be usable after calling this, and any further calls on the Picture will throw an + * IllegalStateException. + * // TODO: Support? + * @hide + */ + public void close() { + if (mNativePicture != 0) { + nativeDestructor(mNativePicture); + mNativePicture = 0; + } + } + @Override protected void finalize() throws Throwable { try { - nativeDestructor(mNativePicture); - mNativePicture = 0; + close(); } finally { super.finalize(); } } + private void verifyValid() { + if (mNativePicture == 0) { + throw new IllegalStateException("Picture is destroyed"); + } + } + /** * To record a picture, call beginRecording() and then draw into the Canvas * that is returned. Nothing we appear on screen, but all of the draw @@ -78,7 +103,9 @@ public class Picture { * that was returned must no longer be used, and nothing should be drawn * into it. */ + @NonNull public Canvas beginRecording(int width, int height) { + verifyValid(); if (mRecordingCanvas != null) { throw new IllegalStateException("Picture already recording, must call #endRecording()"); } @@ -95,6 +122,7 @@ public class Picture { * or {@link Canvas#drawPicture(Picture)} is called. */ public void endRecording() { + verifyValid(); if (mRecordingCanvas != null) { mRequiresHwAcceleration = mRecordingCanvas.mHoldsHwBitmap; mRecordingCanvas = null; @@ -107,7 +135,8 @@ public class Picture { * does not reflect (per se) the content of the picture. */ public int getWidth() { - return nativeGetWidth(mNativePicture); + verifyValid(); + return nativeGetWidth(mNativePicture); } /** @@ -115,7 +144,8 @@ public class Picture { * does not reflect (per se) the content of the picture. */ public int getHeight() { - return nativeGetHeight(mNativePicture); + verifyValid(); + return nativeGetHeight(mNativePicture); } /** @@ -130,6 +160,7 @@ public class Picture { * false otherwise. */ public boolean requiresHardwareAcceleration() { + verifyValid(); return mRequiresHwAcceleration; } @@ -146,7 +177,8 @@ public class Picture { * * @param canvas The picture is drawn to this canvas */ - public void draw(Canvas canvas) { + public void draw(@NonNull Canvas canvas) { + verifyValid(); if (mRecordingCanvas != null) { endRecording(); } @@ -163,12 +195,13 @@ public class Picture { * properly and are highly discouraged. * * @see #writeToStream(java.io.OutputStream) + * @removed * @deprecated The recommended alternative is to not use writeToStream and * instead draw the picture into a Bitmap from which you can persist it as * raw or compressed pixels. */ @Deprecated - public static Picture createFromStream(InputStream stream) { + public static Picture createFromStream(@NonNull InputStream stream) { return new Picture(nativeCreateFromStream(stream, new byte[WORKING_STREAM_STORAGE])); } @@ -179,14 +212,16 @@ public class Picture { * there is no guarantee that the Picture can be successfully reconstructed. * * @see #createFromStream(java.io.InputStream) + * @removed * @deprecated The recommended alternative is to draw the picture into a * Bitmap from which you can persist it as raw or compressed pixels. */ @Deprecated - public void writeToStream(OutputStream stream) { + public void writeToStream(@NonNull OutputStream stream) { + verifyValid(); // do explicit check before calling the native method if (stream == null) { - throw new NullPointerException(); + throw new IllegalArgumentException("stream cannot be null"); } if (!nativeWriteToStream(mNativePicture, stream, new byte[WORKING_STREAM_STORAGE])) { throw new RuntimeException(); @@ -212,7 +247,7 @@ public class Picture { public PictureCanvas(Picture pict, long nativeCanvas) { super(nativeCanvas); mPicture = pict; - // Disable bitmap density scaling. This matches DisplayListCanvas. + // Disable bitmap density scaling. This matches RecordingCanvas. mDensity = 0; } diff --git a/graphics/java/android/graphics/PixelFormat.java b/graphics/java/android/graphics/PixelFormat.java index 96d6eeece0a3..dde757b4eb01 100644 --- a/graphics/java/android/graphics/PixelFormat.java +++ b/graphics/java/android/graphics/PixelFormat.java @@ -90,6 +90,9 @@ public class PixelFormat { public static final int RGBA_F16 = 0x16; public static final int RGBA_1010102 = 0x2B; + /** @hide */ + public static final int HSV_888 = 0x37; + /** * @deprecated use {@link android.graphics.ImageFormat#JPEG * ImageFormat.JPEG} instead. @@ -109,6 +112,7 @@ public class PixelFormat { info.bytesPerPixel = 4; break; case RGB_888: + case HSV_888: info.bitsPerPixel = 24; info.bytesPerPixel = 3; break; @@ -227,6 +231,8 @@ public class PixelFormat { return "RGBA_F16"; case RGBA_1010102: return "RGBA_1010102"; + case HSV_888: + return "HSV_888"; case JPEG: return "JPEG"; default: diff --git a/graphics/java/android/graphics/Point.java b/graphics/java/android/graphics/Point.java index c6b6c668f03a..3614f3bcb3be 100644 --- a/graphics/java/android/graphics/Point.java +++ b/graphics/java/android/graphics/Point.java @@ -16,8 +16,10 @@ package android.graphics; +import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; +import android.util.Size; import android.util.proto.ProtoOutputStream; import java.io.PrintWriter; @@ -37,7 +39,7 @@ public class Point implements Parcelable { this.y = y; } - public Point(Point src) { + public Point(@NonNull Point src) { this.x = src.x; this.y = src.y; } @@ -99,7 +101,7 @@ public class Point implements Parcelable { } /** @hide */ - public void printShortString(PrintWriter pw) { + public void printShortString(@NonNull PrintWriter pw) { pw.print("["); pw.print(x); pw.print(","); pw.print(y); pw.print("]"); } @@ -130,17 +132,18 @@ public class Point implements Parcelable { * @param fieldId Field Id of the Rect as defined in the parent message * @hide */ - public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) { + public void writeToProto(@NonNull ProtoOutputStream protoOutputStream, long fieldId) { final long token = protoOutputStream.start(fieldId); protoOutputStream.write(PointProto.X, x); protoOutputStream.write(PointProto.Y, y); protoOutputStream.end(token); } - public static final Parcelable.Creator<Point> CREATOR = new Parcelable.Creator<Point>() { + public static final @android.annotation.NonNull Parcelable.Creator<Point> CREATOR = new Parcelable.Creator<Point>() { /** * Return a new point from the data in the specified parcel. */ + @Override public Point createFromParcel(Parcel in) { Point r = new Point(); r.readFromParcel(in); @@ -150,6 +153,7 @@ public class Point implements Parcelable { /** * Return an array of rectangles of the specified size. */ + @Override public Point[] newArray(int size) { return new Point[size]; } @@ -161,8 +165,18 @@ public class Point implements Parcelable { * * @param in The parcel to read the point's coordinates from */ - public void readFromParcel(Parcel in) { + public void readFromParcel(@NonNull Parcel in) { x = in.readInt(); y = in.readInt(); } + + /** {@hide} */ + public static @NonNull Point convert(@NonNull Size size) { + return new Point(size.getWidth(), size.getHeight()); + } + + /** {@hide} */ + public static @NonNull Size convert(@NonNull Point point) { + return new Size(point.x, point.y); + } } diff --git a/graphics/java/android/graphics/PointF.java b/graphics/java/android/graphics/PointF.java index 8e4288e35c42..f1f48adc7018 100644 --- a/graphics/java/android/graphics/PointF.java +++ b/graphics/java/android/graphics/PointF.java @@ -16,10 +16,10 @@ package android.graphics; +import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; - /** * PointF holds two float coordinates */ @@ -34,7 +34,7 @@ public class PointF implements Parcelable { this.y = y; } - public PointF(Point p) { + public PointF(@NonNull Point p) { this.x = p.x; this.y = p.y; } @@ -50,7 +50,7 @@ public class PointF implements Parcelable { /** * Set the point's x and y coordinates to the coordinates of p */ - public final void set(PointF p) { + public final void set(@NonNull PointF p) { this.x = p.x; this.y = p.y; } @@ -130,10 +130,11 @@ public class PointF implements Parcelable { out.writeFloat(y); } - public static final Parcelable.Creator<PointF> CREATOR = new Parcelable.Creator<PointF>() { + public static final @android.annotation.NonNull Parcelable.Creator<PointF> CREATOR = new Parcelable.Creator<PointF>() { /** * Return a new point from the data in the specified parcel. */ + @Override public PointF createFromParcel(Parcel in) { PointF r = new PointF(); r.readFromParcel(in); @@ -143,6 +144,7 @@ public class PointF implements Parcelable { /** * Return an array of rectangles of the specified size. */ + @Override public PointF[] newArray(int size) { return new PointF[size]; } @@ -154,7 +156,7 @@ public class PointF implements Parcelable { * * @param in The parcel to read the point's coordinates from */ - public void readFromParcel(Parcel in) { + public void readFromParcel(@NonNull Parcel in) { x = in.readFloat(); y = in.readFloat(); } diff --git a/graphics/java/android/graphics/PorterDuff.java b/graphics/java/android/graphics/PorterDuff.java index d7d3049b0efa..bc1f66fdd5c0 100644 --- a/graphics/java/android/graphics/PorterDuff.java +++ b/graphics/java/android/graphics/PorterDuff.java @@ -16,11 +16,15 @@ package android.graphics; +import android.annotation.UnsupportedAppUsage; + /** * <p>This class contains the list of alpha compositing and blending modes * that can be passed to {@link PorterDuffXfermode}, a specialized implementation * of {@link Paint}'s {@link Paint#setXfermode(Xfermode) transfer mode}. * All the available modes can be found in the {@link Mode} enum.</p> + * + * Consider using {@link BlendMode} instead as it provides a wider variety of tinting options */ public class PorterDuff { /** @@ -364,6 +368,7 @@ public class PorterDuff { /** * @hide */ + @UnsupportedAppUsage public final int nativeInt; } diff --git a/graphics/java/android/graphics/PorterDuffColorFilter.java b/graphics/java/android/graphics/PorterDuffColorFilter.java index 01d5825dd1e0..cc2d3a8969fc 100644 --- a/graphics/java/android/graphics/PorterDuffColorFilter.java +++ b/graphics/java/android/graphics/PorterDuffColorFilter.java @@ -18,6 +18,7 @@ package android.graphics; import android.annotation.ColorInt; import android.annotation.NonNull; +import android.annotation.UnsupportedAppUsage; /** * A color filter that can be used to tint the source pixels using a single @@ -35,8 +36,6 @@ public class PorterDuffColorFilter extends ColorFilter { * @param mode The porter-duff mode that is applied * * @see Color - * @see #setColor(int) - * @see #setMode(android.graphics.PorterDuff.Mode) */ public PorterDuffColorFilter(@ColorInt int color, @NonNull PorterDuff.Mode mode) { mColor = color; @@ -48,68 +47,31 @@ public class PorterDuffColorFilter extends ColorFilter { * is applied. * * @see Color - * @see #setColor(int) * * @hide */ @ColorInt + @UnsupportedAppUsage public int getColor() { return mColor; } /** - * Specifies the color to tint the source pixels with when this color - * filter is applied. - * - * @param color An ARGB {@link Color color} - * - * @see Color - * @see #getColor() - * @see #getMode() - * - * @hide - */ - public void setColor(@ColorInt int color) { - if (mColor != color) { - mColor = color; - discardNativeInstance(); - } - } - - /** * Returns the Porter-Duff mode used to composite this color filter's * color with the source pixel when this filter is applied. * * @see PorterDuff - * @see #setMode(android.graphics.PorterDuff.Mode) * * @hide */ + @UnsupportedAppUsage public PorterDuff.Mode getMode() { return mMode; } - /** - * Specifies the Porter-Duff mode to use when compositing this color - * filter's color with the source pixel at draw time. - * - * @see PorterDuff - * @see #getMode() - * @see #getColor() - * - * @hide - */ - public void setMode(@NonNull PorterDuff.Mode mode) { - if (mode == null) { - throw new IllegalArgumentException("mode must be non-null"); - } - mMode = mode; - discardNativeInstance(); - } - @Override long createNativeInstance() { - return native_CreatePorterDuffFilter(mColor, mMode.nativeInt); + return native_CreateBlendModeFilter(mColor, mMode.nativeInt); } @Override @@ -129,5 +91,5 @@ public class PorterDuffColorFilter extends ColorFilter { return 31 * mMode.hashCode() + mColor; } - private static native long native_CreatePorterDuffFilter(int srcColor, int porterDuffMode); + private static native long native_CreateBlendModeFilter(int srcColor, int blendmode); } diff --git a/graphics/java/android/graphics/PorterDuffXfermode.java b/graphics/java/android/graphics/PorterDuffXfermode.java index 84d953de91db..ff9ff8b0069d 100644 --- a/graphics/java/android/graphics/PorterDuffXfermode.java +++ b/graphics/java/android/graphics/PorterDuffXfermode.java @@ -21,6 +21,7 @@ package android.graphics; * {@link Paint#setXfermode(Xfermode) transfer mode}. Refer to the * documentation of the {@link PorterDuff.Mode} enum for more * information on the available alpha compositing and blending modes.</p> + * */ public class PorterDuffXfermode extends Xfermode { /** diff --git a/graphics/java/android/graphics/RadialGradient.java b/graphics/java/android/graphics/RadialGradient.java index f4b11917a415..acbe3da75247 100644 --- a/graphics/java/android/graphics/RadialGradient.java +++ b/graphics/java/android/graphics/RadialGradient.java @@ -16,30 +16,57 @@ package android.graphics; +import android.annotation.ColorInt; +import android.annotation.ColorLong; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.ColorInt; +import android.annotation.UnsupportedAppUsage; public class RadialGradient extends Shader { - - private static final int TYPE_COLORS_AND_POSITIONS = 1; - private static final int TYPE_COLOR_CENTER_AND_COLOR_EDGE = 2; - - /** - * Type of the RadialGradient: can be either TYPE_COLORS_AND_POSITIONS or - * TYPE_COLOR_CENTER_AND_COLOR_EDGE. - */ - private int mType; - + @UnsupportedAppUsage private float mX; + @UnsupportedAppUsage private float mY; + @UnsupportedAppUsage private float mRadius; - private int[] mColors; + @UnsupportedAppUsage private float[] mPositions; + @UnsupportedAppUsage + private TileMode mTileMode; + + // @ColorInts are replaced by @ColorLongs, but these remain due to @UnsupportedAppUsage. + @UnsupportedAppUsage + @ColorInt + private int[] mColors; + @UnsupportedAppUsage + @ColorInt private int mCenterColor; + @UnsupportedAppUsage + @ColorInt private int mEdgeColor; - private TileMode mTileMode; + @ColorLong + private final long[] mColorLongs; + + /** + * Create a shader that draws a radial gradient given the center and radius. + * + * @param centerX The x-coordinate of the center of the radius + * @param centerY The y-coordinate of the center of the radius + * @param radius Must be positive. The radius of the circle for this gradient. + * @param colors The sRGB colors to be distributed between the center and edge of the circle + * @param stops May be <code>null</code>. Valid values are between <code>0.0f</code> and + * <code>1.0f</code>. The relative position of each corresponding color in + * the colors array. If <code>null</code>, colors are distributed evenly + * between the center and edge of the circle. + * @param tileMode The Shader tiling mode + */ + public RadialGradient(float centerX, float centerY, float radius, + @NonNull @ColorInt int[] colors, @Nullable float[] stops, + @NonNull TileMode tileMode) { + this(centerX, centerY, radius, convertColors(colors), stops, tileMode, + ColorSpace.get(ColorSpace.Named.SRGB)); + } /** * Create a shader that draws a radial gradient given the center and radius. @@ -53,24 +80,36 @@ public class RadialGradient extends Shader { * the colors array. If <code>null</code>, colors are distributed evenly * between the center and edge of the circle. * @param tileMode The Shader tiling mode + * + * @throws IllegalArgumentException if there are less than two colors, the colors do + * not share the same {@link ColorSpace} or do not use a valid one, or {@code stops} + * is not {@code null} and has a different length from {@code colors}. */ public RadialGradient(float centerX, float centerY, float radius, - @NonNull @ColorInt int colors[], @Nullable float stops[], + @NonNull @ColorLong long[] colors, @Nullable float[] stops, @NonNull TileMode tileMode) { + this(centerX, centerY, radius, colors.clone(), stops, tileMode, detectColorSpace(colors)); + } + + /** + * Base constructor. Assumes @param colors is a copy that this object can hold onto, + * and all colors share @param colorSpace. + */ + private RadialGradient(float centerX, float centerY, float radius, + @NonNull @ColorLong long[] colors, @Nullable float[] stops, + @NonNull TileMode tileMode, ColorSpace colorSpace) { + super(colorSpace); + if (radius <= 0) { throw new IllegalArgumentException("radius must be > 0"); } - if (colors.length < 2) { - throw new IllegalArgumentException("needs >= 2 number of colors"); - } if (stops != null && colors.length != stops.length) { throw new IllegalArgumentException("color and position arrays must be of equal length"); } - mType = TYPE_COLORS_AND_POSITIONS; mX = centerX; mY = centerY; mRadius = radius; - mColors = colors.clone(); + mColorLongs = colors; mPositions = stops != null ? stops.clone() : null; mTileMode = tileMode; } @@ -81,54 +120,41 @@ public class RadialGradient extends Shader { * @param centerX The x-coordinate of the center of the radius * @param centerY The y-coordinate of the center of the radius * @param radius Must be positive. The radius of the circle for this gradient - * @param centerColor The color at the center of the circle. - * @param edgeColor The color at the edge of the circle. + * @param centerColor The sRGB color at the center of the circle. + * @param edgeColor The sRGB color at the edge of the circle. * @param tileMode The Shader tiling mode */ public RadialGradient(float centerX, float centerY, float radius, @ColorInt int centerColor, @ColorInt int edgeColor, @NonNull TileMode tileMode) { - if (radius <= 0) { - throw new IllegalArgumentException("radius must be > 0"); - } - mType = TYPE_COLOR_CENTER_AND_COLOR_EDGE; - mX = centerX; - mY = centerY; - mRadius = radius; - mCenterColor = centerColor; - mEdgeColor = edgeColor; - mTileMode = tileMode; - } - - @Override - long createNativeInstance(long nativeMatrix) { - if (mType == TYPE_COLORS_AND_POSITIONS) { - return nativeCreate1(nativeMatrix, mX, mY, mRadius, - mColors, mPositions, mTileMode.nativeInt); - } else { // TYPE_COLOR_CENTER_AND_COLOR_EDGE - return nativeCreate2(nativeMatrix, mX, mY, mRadius, - mCenterColor, mEdgeColor, mTileMode.nativeInt); - } + this(centerX, centerY, radius, Color.pack(centerColor), Color.pack(edgeColor), tileMode); } /** - * @hide + * Create a shader that draws a radial gradient given the center and radius. + * + * @param centerX The x-coordinate of the center of the radius + * @param centerY The y-coordinate of the center of the radius + * @param radius Must be positive. The radius of the circle for this gradient + * @param centerColor The color at the center of the circle. + * @param edgeColor The color at the edge of the circle. + * @param tileMode The Shader tiling mode + * + * @throws IllegalArgumentException if the colors do + * not share the same {@link ColorSpace} or do not use a valid one. */ + public RadialGradient(float centerX, float centerY, float radius, + @ColorLong long centerColor, @ColorLong long edgeColor, @NonNull TileMode tileMode) { + this(centerX, centerY, radius, new long[] {centerColor, edgeColor}, null, tileMode); + } + @Override - protected Shader copy() { - final RadialGradient copy; - if (mType == TYPE_COLORS_AND_POSITIONS) { - copy = new RadialGradient(mX, mY, mRadius, mColors.clone(), - mPositions != null ? mPositions.clone() : null, mTileMode); - } else { // TYPE_COLOR_CENTER_AND_COLOR_EDGE - copy = new RadialGradient(mX, mY, mRadius, mCenterColor, mEdgeColor, mTileMode); - } - copyLocalMatrix(copy); - return copy; + long createNativeInstance(long nativeMatrix) { + return nativeCreate(nativeMatrix, mX, mY, mRadius, + mColorLongs, mPositions, mTileMode.nativeInt, + colorSpace().getNativeInstance()); } - private static native long nativeCreate1(long matrix, float x, float y, float radius, - int colors[], float positions[], int tileMode); - private static native long nativeCreate2(long matrix, float x, float y, float radius, - int color0, int color1, int tileMode); + private static native long nativeCreate(long matrix, float x, float y, float radius, + @ColorLong long[] colors, float[] positions, int tileMode, long colorSpaceHandle); } diff --git a/graphics/java/android/graphics/RecordingCanvas.java b/graphics/java/android/graphics/RecordingCanvas.java new file mode 100644 index 000000000000..c0e0a24583ec --- /dev/null +++ b/graphics/java/android/graphics/RecordingCanvas.java @@ -0,0 +1,320 @@ +/* + * Copyright (C) 2010 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.graphics; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.Pools.SynchronizedPool; +import android.view.DisplayListCanvas; +import android.view.TextureLayer; + +import dalvik.annotation.optimization.CriticalNative; +import dalvik.annotation.optimization.FastNative; + +/** + * A Canvas implementation that records view system drawing operations for deferred rendering. + * This is used in combination with RenderNode. This class keeps a list of all the Paint and + * Bitmap objects that it draws, preventing the backing memory of Bitmaps from being released while + * the RecordingCanvas is still holding a native reference to the memory. + * + * This is obtained by calling {@link RenderNode#beginRecording()} and is valid until the matching + * {@link RenderNode#endRecording()} is called. It must not be retained beyond that as it is + * internally reused. + */ +public final class RecordingCanvas extends DisplayListCanvas { + // The recording canvas pool should be large enough to handle a deeply nested + // view hierarchy because display lists are generated recursively. + private static final int POOL_LIMIT = 25; + + /** @hide */ + public static final int MAX_BITMAP_SIZE = 100 * 1024 * 1024; // 100 MB + + private static final SynchronizedPool<RecordingCanvas> sPool = + new SynchronizedPool<>(POOL_LIMIT); + + /** + * TODO: Temporarily exposed for RenderNodeAnimator(Set) + * @hide */ + public RenderNode mNode; + private int mWidth; + private int mHeight; + + /** @hide */ + static RecordingCanvas obtain(@NonNull RenderNode node, int width, int height) { + if (node == null) throw new IllegalArgumentException("node cannot be null"); + RecordingCanvas canvas = sPool.acquire(); + if (canvas == null) { + canvas = new RecordingCanvas(node, width, height); + } else { + nResetDisplayListCanvas(canvas.mNativeCanvasWrapper, node.mNativeRenderNode, + width, height); + } + canvas.mNode = node; + canvas.mWidth = width; + canvas.mHeight = height; + return canvas; + } + + /** @hide */ + void recycle() { + mNode = null; + sPool.release(this); + } + + /** @hide */ + long finishRecording() { + return nFinishRecording(mNativeCanvasWrapper); + } + + /** @hide */ + @Override + public boolean isRecordingFor(Object o) { + return o == mNode; + } + + /////////////////////////////////////////////////////////////////////////// + // Constructors + /////////////////////////////////////////////////////////////////////////// + + /** @hide */ + protected RecordingCanvas(@NonNull RenderNode node, int width, int height) { + super(nCreateDisplayListCanvas(node.mNativeRenderNode, width, height)); + mDensity = 0; // disable bitmap density scaling + } + + /////////////////////////////////////////////////////////////////////////// + // Canvas management + /////////////////////////////////////////////////////////////////////////// + + + @Override + public void setDensity(int density) { + // drop silently, since RecordingCanvas doesn't perform density scaling + } + + @Override + public boolean isHardwareAccelerated() { + return true; + } + + @Override + public void setBitmap(Bitmap bitmap) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isOpaque() { + return false; + } + + @Override + public int getWidth() { + return mWidth; + } + + @Override + public int getHeight() { + return mHeight; + } + + @Override + public int getMaximumBitmapWidth() { + return nGetMaximumTextureWidth(); + } + + @Override + public int getMaximumBitmapHeight() { + return nGetMaximumTextureHeight(); + } + + /////////////////////////////////////////////////////////////////////////// + // Setup + /////////////////////////////////////////////////////////////////////////// + + @Override + public void enableZ() { + nInsertReorderBarrier(mNativeCanvasWrapper, true); + } + + @Override + public void disableZ() { + nInsertReorderBarrier(mNativeCanvasWrapper, false); + } + + /////////////////////////////////////////////////////////////////////////// + // Functor + /////////////////////////////////////////////////////////////////////////// + + /** + * Records the functor specified with the drawGLFunction function pointer. This is + * functionality used by webview for calling into their renderer from our display lists. + * + * @param drawGLFunction A native function pointer + * + * @hide + * @deprecated Use {@link #drawWebViewFunctor(int)} + */ + @Deprecated + public void callDrawGLFunction2(long drawGLFunction) { + nCallDrawGLFunction(mNativeCanvasWrapper, drawGLFunction, null); + } + + /** + * Records the functor specified with the drawGLFunction function pointer. This is + * functionality used by webview for calling into their renderer from our display lists. + * + * @param drawGLFunctor A native function pointer + * @param releasedCallback Called when the display list is destroyed, and thus + * the functor is no longer referenced by this canvas's display list. + * + * NOTE: The callback does *not* necessarily mean that there are no longer + * any references to the functor, just that the reference from this specific + * canvas's display list has been released. + * + * @hide + * @deprecated Use {@link #drawWebViewFunctor(int)} + */ + @Deprecated + public void drawGLFunctor2(long drawGLFunctor, @Nullable Runnable releasedCallback) { + nCallDrawGLFunction(mNativeCanvasWrapper, drawGLFunctor, releasedCallback); + } + + /** + * Calls the provided functor that was created via WebViewFunctor_create() + * @hide + */ + public void drawWebViewFunctor(int functor) { + nDrawWebViewFunctor(mNativeCanvasWrapper, functor); + } + + /////////////////////////////////////////////////////////////////////////// + // Display list + /////////////////////////////////////////////////////////////////////////// + + /** + * Draws the specified display list onto this canvas. + * + * @param renderNode The RenderNode to draw. + */ + @Override + public void drawRenderNode(@NonNull RenderNode renderNode) { + nDrawRenderNode(mNativeCanvasWrapper, renderNode.mNativeRenderNode); + } + + /////////////////////////////////////////////////////////////////////////// + // Hardware layer + /////////////////////////////////////////////////////////////////////////// + + /** + * Draws the specified layer onto this canvas. + * + * @param layer The layer to composite on this canvas + * @hide + */ + public void drawTextureLayer(TextureLayer layer) { + nDrawTextureLayer(mNativeCanvasWrapper, layer.getLayerHandle()); + } + + /////////////////////////////////////////////////////////////////////////// + // Drawing + /////////////////////////////////////////////////////////////////////////// + + /** + * Draws a circle + * + * @param cx + * @param cy + * @param radius + * @param paint + * + * @hide + */ + public void drawCircle(CanvasProperty<Float> cx, CanvasProperty<Float> cy, + CanvasProperty<Float> radius, CanvasProperty<Paint> paint) { + nDrawCircle(mNativeCanvasWrapper, cx.getNativeContainer(), cy.getNativeContainer(), + radius.getNativeContainer(), paint.getNativeContainer()); + } + + /** + * Draws a round rect + * + * @param left + * @param top + * @param right + * @param bottom + * @param rx + * @param ry + * @param paint + * + * @hide + */ + public void drawRoundRect(CanvasProperty<Float> left, CanvasProperty<Float> top, + CanvasProperty<Float> right, CanvasProperty<Float> bottom, CanvasProperty<Float> rx, + CanvasProperty<Float> ry, CanvasProperty<Paint> paint) { + nDrawRoundRect(mNativeCanvasWrapper, left.getNativeContainer(), top.getNativeContainer(), + right.getNativeContainer(), bottom.getNativeContainer(), + rx.getNativeContainer(), ry.getNativeContainer(), + paint.getNativeContainer()); + } + + /** @hide */ + @Override + protected void throwIfCannotDraw(Bitmap bitmap) { + super.throwIfCannotDraw(bitmap); + int bitmapSize = bitmap.getByteCount(); + if (bitmapSize > MAX_BITMAP_SIZE) { + throw new RuntimeException( + "Canvas: trying to draw too large(" + bitmapSize + "bytes) bitmap."); + } + } + + + // ------------------ Fast JNI ------------------------ + + @FastNative + private static native void nCallDrawGLFunction(long renderer, + long drawGLFunction, Runnable releasedCallback); + + + // ------------------ Critical JNI ------------------------ + + @CriticalNative + private static native long nCreateDisplayListCanvas(long node, int width, int height); + @CriticalNative + private static native void nResetDisplayListCanvas(long canvas, long node, + int width, int height); + @CriticalNative + private static native int nGetMaximumTextureWidth(); + @CriticalNative + private static native int nGetMaximumTextureHeight(); + @CriticalNative + private static native void nInsertReorderBarrier(long renderer, boolean enableReorder); + @CriticalNative + private static native long nFinishRecording(long renderer); + @CriticalNative + private static native void nDrawRenderNode(long renderer, long renderNode); + @CriticalNative + private static native void nDrawTextureLayer(long renderer, long layer); + @CriticalNative + private static native void nDrawCircle(long renderer, long propCx, + long propCy, long propRadius, long propPaint); + @CriticalNative + private static native void nDrawRoundRect(long renderer, long propLeft, long propTop, + long propRight, long propBottom, long propRx, long propRy, long propPaint); + @CriticalNative + private static native void nDrawWebViewFunctor(long canvas, int functor); +} diff --git a/graphics/java/android/graphics/Rect.java b/graphics/java/android/graphics/Rect.java index 3843cb91154c..d47f682ec2d3 100644 --- a/graphics/java/android/graphics/Rect.java +++ b/graphics/java/android/graphics/Rect.java @@ -17,12 +17,17 @@ package android.graphics; import android.annotation.CheckResult; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; - import android.text.TextUtils; +import android.util.proto.ProtoInputStream; import android.util.proto.ProtoOutputStream; +import android.util.proto.WireTypeMismatchException; + +import java.io.IOException; import java.io.PrintWriter; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -89,7 +94,21 @@ public final class Rect implements Parcelable { * @param r The rectangle whose coordinates are copied into the new * rectangle. */ - public Rect(Rect r) { + public Rect(@Nullable Rect r) { + if (r == null) { + left = top = right = bottom = 0; + } else { + left = r.left; + top = r.top; + right = r.right; + bottom = r.bottom; + } + } + + /** + * @hide + */ + public Rect(@Nullable Insets r) { if (r == null) { left = top = right = bottom = 0; } else { @@ -140,6 +159,7 @@ public final class Rect implements Parcelable { /** * Return a string representation of the rectangle in a compact form. */ + @NonNull public String toShortString() { return toShortString(new StringBuilder(32)); } @@ -148,7 +168,8 @@ public final class Rect implements Parcelable { * Return a string representation of the rectangle in a compact form. * @hide */ - public String toShortString(StringBuilder sb) { + @NonNull + public String toShortString(@NonNull StringBuilder sb) { sb.setLength(0); sb.append('['); sb.append(left); sb.append(','); sb.append(top); sb.append("]["); sb.append(right); @@ -164,6 +185,7 @@ public final class Rect implements Parcelable { * * @return Returns a new String of the form "left top right bottom" */ + @NonNull public String flattenToString() { StringBuilder sb = new StringBuilder(32); // WARNING: Do not change the format of this string, it must be @@ -182,7 +204,8 @@ public final class Rect implements Parcelable { * Returns a Rect from a string of the form returned by {@link #flattenToString}, * or null if the string is not of that form. */ - public static Rect unflattenFromString(String str) { + @Nullable + public static Rect unflattenFromString(@Nullable String str) { if (TextUtils.isEmpty(str)) { return null; } @@ -201,7 +224,8 @@ public final class Rect implements Parcelable { * Print short representation to given writer. * @hide */ - public void printShortString(PrintWriter pw) { + @UnsupportedAppUsage + public void printShortString(@NonNull PrintWriter pw) { pw.print('['); pw.print(left); pw.print(','); pw.print(top); pw.print("]["); pw.print(right); pw.print(','); pw.print(bottom); pw.print(']'); @@ -215,7 +239,7 @@ public final class Rect implements Parcelable { * @param fieldId Field Id of the Rect as defined in the parent message * @hide */ - public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) { + public void writeToProto(@NonNull ProtoOutputStream protoOutputStream, long fieldId) { final long token = protoOutputStream.start(fieldId); protoOutputStream.write(RectProto.LEFT, left); protoOutputStream.write(RectProto.TOP, top); @@ -225,6 +249,40 @@ public final class Rect implements Parcelable { } /** + * Read from a protocol buffer input stream. + * Protocol buffer message definition at {@link android.graphics.RectProto} + * + * @param proto Stream to read the Rect object from. + * @param fieldId Field Id of the Rect as defined in the parent message + * @hide + */ + public void readFromProto(@NonNull ProtoInputStream proto, long fieldId) throws IOException, + WireTypeMismatchException { + final long token = proto.start(fieldId); + try { + while (proto.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (proto.getFieldNumber()) { + case (int) RectProto.LEFT: + left = proto.readInt(RectProto.LEFT); + break; + case (int) RectProto.TOP: + top = proto.readInt(RectProto.TOP); + break; + case (int) RectProto.RIGHT: + right = proto.readInt(RectProto.RIGHT); + break; + case (int) RectProto.BOTTOM: + bottom = proto.readInt(RectProto.BOTTOM); + break; + } + } + } finally { + // Let caller handle any exceptions + proto.end(token); + } + } + + /** * Returns true if the rectangle is empty (left >= right or top >= bottom) */ public final boolean isEmpty() { @@ -309,7 +367,7 @@ public final class Rect implements Parcelable { * @param src The rectangle whose coordinates are copied into this * rectangle. */ - public void set(Rect src) { + public void set(@NonNull Rect src) { this.left = src.left; this.top = src.top; this.right = src.right; @@ -366,7 +424,19 @@ public final class Rect implements Parcelable { * @hide * @param insets The rectangle specifying the insets on all side. */ - public void inset(Rect insets) { + public void inset(@NonNull Rect insets) { + left += insets.left; + top += insets.top; + right -= insets.right; + bottom -= insets.bottom; + } + + /** + * Insets the rectangle on all sides specified by the dimensions of {@code insets}. + * @hide + * @param insets The insets to inset the rect by. + */ + public void inset(Insets insets) { left += insets.left; top += insets.top; right -= insets.right; @@ -432,7 +502,7 @@ public final class Rect implements Parcelable { * @return true iff the specified rectangle r is inside or equal to this * rectangle */ - public boolean contains(Rect r) { + public boolean contains(@NonNull Rect r) { // check for empty first return this.left < this.right && this.top < this.bottom // now check for containment @@ -481,7 +551,7 @@ public final class Rect implements Parcelable { * return false and do not change this rectangle. */ @CheckResult - public boolean intersect(Rect r) { + public boolean intersect(@NonNull Rect r) { return intersect(r.left, r.top, r.right, r.bottom); } @@ -491,7 +561,7 @@ public final class Rect implements Parcelable { * @see #inset(int, int, int, int) but without checking if the rects overlap. * @hide */ - public void intersectUnchecked(Rect other) { + public void intersectUnchecked(@NonNull Rect other) { left = Math.max(left, other.left); top = Math.max(top, other.top); right = Math.min(right, other.right); @@ -511,7 +581,7 @@ public final class Rect implements Parcelable { * false and do not change this rectangle. */ @CheckResult - public boolean setIntersect(Rect a, Rect b) { + public boolean setIntersect(@NonNull Rect a, @NonNull Rect b) { if (a.left < b.right && b.left < a.right && a.top < b.bottom && b.top < a.bottom) { left = Math.max(a.left, b.left); top = Math.max(a.top, b.top); @@ -550,7 +620,7 @@ public final class Rect implements Parcelable { * @return true iff the two specified rectangles intersect. In no event are * either of the rectangles modified. */ - public static boolean intersects(Rect a, Rect b) { + public static boolean intersects(@NonNull Rect a, @NonNull Rect b) { return a.left < b.right && b.left < a.right && a.top < b.bottom && b.top < a.bottom; } @@ -587,7 +657,7 @@ public final class Rect implements Parcelable { * * @param r The rectangle being unioned with this rectangle */ - public void union(Rect r) { + public void union(@NonNull Rect r) { union(r.left, r.top, r.right, r.bottom); } @@ -634,6 +704,7 @@ public final class Rect implements Parcelable { /** * Parcelable interface methods */ + @Override public int describeContents() { return 0; } @@ -643,6 +714,7 @@ public final class Rect implements Parcelable { * a parcel, use readFromParcel() * @param out The parcel to write the rectangle's coordinates into */ + @Override public void writeToParcel(Parcel out, int flags) { out.writeInt(left); out.writeInt(top); @@ -650,10 +722,11 @@ public final class Rect implements Parcelable { out.writeInt(bottom); } - public static final Parcelable.Creator<Rect> CREATOR = new Parcelable.Creator<Rect>() { + public static final @android.annotation.NonNull Parcelable.Creator<Rect> CREATOR = new Parcelable.Creator<Rect>() { /** * Return a new rectangle from the data in the specified parcel. */ + @Override public Rect createFromParcel(Parcel in) { Rect r = new Rect(); r.readFromParcel(in); @@ -663,6 +736,7 @@ public final class Rect implements Parcelable { /** * Return an array of rectangles of the specified size. */ + @Override public Rect[] newArray(int size) { return new Rect[size]; } @@ -674,7 +748,7 @@ public final class Rect implements Parcelable { * * @param in The parcel to read the rectangle's coordinates from */ - public void readFromParcel(Parcel in) { + public void readFromParcel(@NonNull Parcel in) { left = in.readInt(); top = in.readInt(); right = in.readInt(); @@ -685,6 +759,7 @@ public final class Rect implements Parcelable { * Scales up the rect by the given scale. * @hide */ + @UnsupportedAppUsage public void scale(float scale) { if (scale != 1.0f) { left = (int) (left * scale + 0.5f); diff --git a/graphics/java/android/graphics/RectF.java b/graphics/java/android/graphics/RectF.java index b49054550956..1d294d51a235 100644 --- a/graphics/java/android/graphics/RectF.java +++ b/graphics/java/android/graphics/RectF.java @@ -16,15 +16,18 @@ package android.graphics; -import java.io.PrintWriter; - +import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; + import com.android.internal.util.FastMath; +import java.io.PrintWriter; + /** * RectF holds four float coordinates for a rectangle. The rectangle is - * represented by the coordinates of its 4 edges (left, top, right bottom). + * represented by the coordinates of its 4 edges (left, top, right, bottom). * These fields can be accessed directly. Use width() and height() to retrieve * the rectangle's width and height. Note: most methods do not check to see that * the coordinates are sorted correctly (i.e. left <= right and top <= bottom). @@ -64,7 +67,7 @@ public class RectF implements Parcelable { * @param r The rectangle whose coordinates are copied into the new * rectangle. */ - public RectF(RectF r) { + public RectF(@Nullable RectF r) { if (r == null) { left = top = right = bottom = 0.0f; } else { @@ -75,7 +78,7 @@ public class RectF implements Parcelable { } } - public RectF(Rect r) { + public RectF(@Nullable Rect r) { if (r == null) { left = top = right = bottom = 0.0f; } else { @@ -104,6 +107,7 @@ public class RectF implements Parcelable { return result; } + @Override public String toString() { return "RectF(" + left + ", " + top + ", " + right + ", " + bottom + ")"; @@ -112,6 +116,7 @@ public class RectF implements Parcelable { /** * Return a string representation of the rectangle in a compact form. */ + @NonNull public String toShortString() { return toShortString(new StringBuilder(32)); } @@ -120,7 +125,8 @@ public class RectF implements Parcelable { * Return a string representation of the rectangle in a compact form. * @hide */ - public String toShortString(StringBuilder sb) { + @NonNull + public String toShortString(@NonNull StringBuilder sb) { sb.setLength(0); sb.append('['); sb.append(left); sb.append(','); sb.append(top); sb.append("]["); sb.append(right); @@ -132,7 +138,7 @@ public class RectF implements Parcelable { * Print short representation to given writer. * @hide */ - public void printShortString(PrintWriter pw) { + public void printShortString(@NonNull PrintWriter pw) { pw.print('['); pw.print(left); pw.print(','); pw.print(top); pw.print("]["); pw.print(right); pw.print(','); pw.print(bottom); pw.print(']'); @@ -207,7 +213,7 @@ public class RectF implements Parcelable { * @param src The rectangle whose coordinates are copied into this * rectangle. */ - public void set(RectF src) { + public void set(@NonNull RectF src) { this.left = src.left; this.top = src.top; this.right = src.right; @@ -220,7 +226,7 @@ public class RectF implements Parcelable { * @param src The rectangle whose coordinates are copied into this * rectangle. */ - public void set(Rect src) { + public void set(@NonNull Rect src) { this.left = src.left; this.top = src.top; this.right = src.right; @@ -315,7 +321,7 @@ public class RectF implements Parcelable { * @return true iff the specified rectangle r is inside or equal to this * rectangle */ - public boolean contains(RectF r) { + public boolean contains(@NonNull RectF r) { // check for empty first return this.left < this.right && this.top < this.bottom // now check for containment @@ -372,7 +378,7 @@ public class RectF implements Parcelable { * (and this rectangle is then set to that intersection) else * return false and do not change this rectangle. */ - public boolean intersect(RectF r) { + public boolean intersect(@NonNull RectF r) { return intersect(r.left, r.top, r.right, r.bottom); } @@ -388,7 +394,7 @@ public class RectF implements Parcelable { * this rectangle to that intersection. If they do not, return * false and do not change this rectangle. */ - public boolean setIntersect(RectF a, RectF b) { + public boolean setIntersect(@NonNull RectF a, @NonNull RectF b) { if (a.left < b.right && b.left < a.right && a.top < b.bottom && b.top < a.bottom) { left = Math.max(a.left, b.left); @@ -430,7 +436,7 @@ public class RectF implements Parcelable { * @return true iff the two specified rectangles intersect. In no event are * either of the rectangles modified. */ - public static boolean intersects(RectF a, RectF b) { + public static boolean intersects(@NonNull RectF a, @NonNull RectF b) { return a.left < b.right && b.left < a.right && a.top < b.bottom && b.top < a.bottom; } @@ -439,7 +445,7 @@ public class RectF implements Parcelable { * Set the dst integer Rect by rounding this rectangle's coordinates * to their nearest integer values. */ - public void round(Rect dst) { + public void round(@NonNull Rect dst) { dst.set(FastMath.round(left), FastMath.round(top), FastMath.round(right), FastMath.round(bottom)); } @@ -448,7 +454,7 @@ public class RectF implements Parcelable { * Set the dst integer Rect by rounding "out" this rectangle, choosing the * floor of top and left, and the ceiling of right and bottom. */ - public void roundOut(Rect dst) { + public void roundOut(@NonNull Rect dst) { dst.set((int) Math.floor(left), (int) Math.floor(top), (int) Math.ceil(right), (int) Math.ceil(bottom)); } @@ -490,7 +496,7 @@ public class RectF implements Parcelable { * * @param r The rectangle being unioned with this rectangle */ - public void union(RectF r) { + public void union(@NonNull RectF r) { union(r.left, r.top, r.right, r.bottom); } @@ -537,6 +543,7 @@ public class RectF implements Parcelable { /** * Parcelable interface methods */ + @Override public int describeContents() { return 0; } @@ -546,6 +553,7 @@ public class RectF implements Parcelable { * a parcel, use readFromParcel() * @param out The parcel to write the rectangle's coordinates into */ + @Override public void writeToParcel(Parcel out, int flags) { out.writeFloat(left); out.writeFloat(top); @@ -553,10 +561,11 @@ public class RectF implements Parcelable { out.writeFloat(bottom); } - public static final Parcelable.Creator<RectF> CREATOR = new Parcelable.Creator<RectF>() { + public static final @android.annotation.NonNull Parcelable.Creator<RectF> CREATOR = new Parcelable.Creator<RectF>() { /** * Return a new rectangle from the data in the specified parcel. */ + @Override public RectF createFromParcel(Parcel in) { RectF r = new RectF(); r.readFromParcel(in); @@ -566,6 +575,7 @@ public class RectF implements Parcelable { /** * Return an array of rectangles of the specified size. */ + @Override public RectF[] newArray(int size) { return new RectF[size]; } @@ -577,7 +587,7 @@ public class RectF implements Parcelable { * * @param in The parcel to read the rectangle's coordinates from */ - public void readFromParcel(Parcel in) { + public void readFromParcel(@NonNull Parcel in) { left = in.readFloat(); top = in.readFloat(); right = in.readFloat(); diff --git a/graphics/java/android/graphics/Region.java b/graphics/java/android/graphics/Region.java index dca6d9ed3b2f..ec7f7a05b685 100644 --- a/graphics/java/android/graphics/Region.java +++ b/graphics/java/android/graphics/Region.java @@ -16,6 +16,8 @@ package android.graphics; +import android.annotation.NonNull; +import android.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; import android.util.Pools.SynchronizedPool; @@ -30,6 +32,7 @@ public class Region implements Parcelable { /** * @hide */ + @UnsupportedAppUsage public long mNativeRegion; // the native values for these must match up with the enum in SkRegion.h @@ -48,6 +51,7 @@ public class Region implements Parcelable { /** * @hide */ + @UnsupportedAppUsage public final int nativeInt; } @@ -59,14 +63,14 @@ public class Region implements Parcelable { /** Return a copy of the specified region */ - public Region(Region region) { + public Region(@NonNull Region region) { this(nativeConstructor()); nativeSetRegion(mNativeRegion, region.mNativeRegion); } /** Return a region set to the specified rectangle */ - public Region(Rect r) { + public Region(@NonNull Rect r) { mNativeRegion = nativeConstructor(); nativeSetRect(mNativeRegion, r.left, r.top, r.right, r.bottom); } @@ -86,14 +90,14 @@ public class Region implements Parcelable { /** Set the region to the specified region. */ - public boolean set(Region region) { + public boolean set(@NonNull Region region) { nativeSetRegion(mNativeRegion, region.mNativeRegion); return true; } /** Set the region to the specified rectangle */ - public boolean set(Rect r) { + public boolean set(@NonNull Rect r) { return nativeSetRect(mNativeRegion, r.left, r.top, r.right, r.bottom); } @@ -109,7 +113,7 @@ public class Region implements Parcelable { * that is identical to the pixels that would be drawn by the path * (with no antialiasing). */ - public boolean setPath(Path path, Region clip) { + public boolean setPath(@NonNull Path path, @NonNull Region clip) { return nativeSetPath(mNativeRegion, path.readOnlyNI(), clip.mNativeRegion); } @@ -132,6 +136,7 @@ public class Region implements Parcelable { * Return a new Rect set to the bounds of the region. If the region is * empty, the Rect will be set to [0, 0, 0, 0] */ + @NonNull public Rect getBounds() { Rect r = new Rect(); nativeGetBounds(mNativeRegion, r); @@ -142,7 +147,7 @@ public class Region implements Parcelable { * Set the Rect to the bounds of the region. If the region is empty, the * Rect will be set to [0, 0, 0, 0] */ - public boolean getBounds(Rect r) { + public boolean getBounds(@NonNull Rect r) { if (r == null) { throw new NullPointerException(); } @@ -153,6 +158,7 @@ public class Region implements Parcelable { * Return the boundary of the region as a new Path. If the region is empty, * the path will also be empty. */ + @NonNull public Path getBoundaryPath() { Path path = new Path(); nativeGetBoundaryPath(mNativeRegion, path.mutateNI()); @@ -163,7 +169,7 @@ public class Region implements Parcelable { * Set the path to the boundary of the region. If the region is empty, the * path will also be empty. */ - public boolean getBoundaryPath(Path path) { + public boolean getBoundaryPath(@NonNull Path path) { return nativeGetBoundaryPath(mNativeRegion, path.mutateNI()); } @@ -178,7 +184,7 @@ public class Region implements Parcelable { * that the rectangle is not contained by this region, but return true is a * guarantee that the rectangle is contained by this region. */ - public boolean quickContains(Rect r) { + public boolean quickContains(@NonNull Rect r) { return quickContains(r.left, r.top, r.right, r.bottom); } @@ -196,7 +202,7 @@ public class Region implements Parcelable { * not intersect the region. Returning false is not a guarantee that they * intersect, but returning true is a guarantee that they do not. */ - public boolean quickReject(Rect r) { + public boolean quickReject(@NonNull Rect r) { return quickReject(r.left, r.top, r.right, r.bottom); } @@ -236,6 +242,7 @@ public class Region implements Parcelable { * * @hide */ + @UnsupportedAppUsage public void scale(float scale) { scale(scale, null); } @@ -247,7 +254,7 @@ public class Region implements Parcelable { */ public native void scale(float scale, Region dst); - public final boolean union(Rect r) { + public final boolean union(@NonNull Rect r) { return op(r, Op.UNION); } @@ -255,7 +262,7 @@ public class Region implements Parcelable { * Perform the specified Op on this region and the specified rect. Return * true if the result of the op is not empty. */ - public boolean op(Rect r, Op op) { + public boolean op(@NonNull Rect r, @NonNull Op op) { return nativeOp(mNativeRegion, r.left, r.top, r.right, r.bottom, op.nativeInt); } @@ -264,7 +271,7 @@ public class Region implements Parcelable { * Perform the specified Op on this region and the specified rect. Return * true if the result of the op is not empty. */ - public boolean op(int left, int top, int right, int bottom, Op op) { + public boolean op(int left, int top, int right, int bottom, @NonNull Op op) { return nativeOp(mNativeRegion, left, top, right, bottom, op.nativeInt); } @@ -273,7 +280,7 @@ public class Region implements Parcelable { * Perform the specified Op on this region and the specified region. Return * true if the result of the op is not empty. */ - public boolean op(Region region, Op op) { + public boolean op(@NonNull Region region, @NonNull Op op) { return op(this, region, op); } @@ -281,7 +288,7 @@ public class Region implements Parcelable { * Set this region to the result of performing the Op on the specified rect * and region. Return true if the result is not empty. */ - public boolean op(Rect rect, Region region, Op op) { + public boolean op(@NonNull Rect rect, @NonNull Region region, @NonNull Op op) { return nativeOp(mNativeRegion, rect, region.mNativeRegion, op.nativeInt); } @@ -290,11 +297,12 @@ public class Region implements Parcelable { * Set this region to the result of performing the Op on the specified * regions. Return true if the result is not empty. */ - public boolean op(Region region1, Region region2, Op op) { + public boolean op(@NonNull Region region1, @NonNull Region region2, @NonNull Op op) { return nativeOp(mNativeRegion, region1.mNativeRegion, region2.mNativeRegion, op.nativeInt); } + @Override public String toString() { return nativeToString(mNativeRegion); } @@ -304,6 +312,7 @@ public class Region implements Parcelable { * * @hide */ + @NonNull public static Region obtain() { Region region = sPool.acquire(); return (region != null) ? region : new Region(); @@ -316,7 +325,8 @@ public class Region implements Parcelable { * * @hide */ - public static Region obtain(Region other) { + @NonNull + public static Region obtain(@NonNull Region other) { Region region = obtain(); region.set(other); return region; @@ -327,6 +337,7 @@ public class Region implements Parcelable { * * @hide */ + @UnsupportedAppUsage public void recycle() { setEmpty(); sPool.release(this); @@ -334,13 +345,14 @@ public class Region implements Parcelable { ////////////////////////////////////////////////////////////////////////// - public static final Parcelable.Creator<Region> CREATOR + public static final @android.annotation.NonNull Parcelable.Creator<Region> CREATOR = new Parcelable.Creator<Region>() { /** * Rebuild a Region previously stored with writeToParcel(). * @param p Parcel object to read the region from * @return a new region created from the data in the parcel */ + @Override public Region createFromParcel(Parcel p) { long ni = nativeCreateFromParcel(p); if (ni == 0) { @@ -348,11 +360,13 @@ public class Region implements Parcelable { } return new Region(ni); } + @Override public Region[] newArray(int size) { return new Region[size]; } }; + @Override public int describeContents() { return 0; } @@ -362,6 +376,7 @@ public class Region implements Parcelable { * rebuilt from the parcel by calling CREATOR.createFromParcel(). * @param p Parcel object to write the region data into */ + @Override public void writeToParcel(Parcel p, int flags) { if (!nativeWriteToParcel(mNativeRegion, p)) { throw new RuntimeException(); @@ -377,6 +392,7 @@ public class Region implements Parcelable { return nativeEquals(mNativeRegion, peer.mNativeRegion); } + @Override protected void finalize() throws Throwable { try { nativeDestructor(mNativeRegion); @@ -395,6 +411,7 @@ public class Region implements Parcelable { /* add dummy parameter so constructor can be called from jni without triggering 'not cloneable' exception */ + @UnsupportedAppUsage private Region(long ni, int dummy) { this(ni); } diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java new file mode 100644 index 000000000000..ae7fe6c46f2f --- /dev/null +++ b/graphics/java/android/graphics/RenderNode.java @@ -0,0 +1,1746 @@ +/* + * Copyright (C) 2010 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.graphics; + +import android.annotation.BytesLong; +import android.annotation.ColorInt; +import android.annotation.FloatRange; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.view.NativeVectorDrawableAnimator; +import android.view.RenderNodeAnimator; +import android.view.Surface; +import android.view.View; + +import com.android.internal.util.ArrayUtils; + +import dalvik.annotation.optimization.CriticalNative; +import dalvik.annotation.optimization.FastNative; + +import libcore.util.NativeAllocationRegistry; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * <p>RenderNode is used to build hardware accelerated rendering hierarchies. Each RenderNode + * contains both a display list as well as a set of properties that affect the rendering of the + * display list. RenderNodes are used internally for all Views by default and are not typically + * used directly.</p> + * + * <p>RenderNodes are used to divide up the rendering content of a complex scene into smaller + * pieces that can then be updated individually more cheaply. Updating part of the scene only needs + * to update the display list or properties of a small number of RenderNode instead of redrawing + * everything from scratch. A RenderNode only needs its display list re-recorded when its content + * alone should be changed. RenderNodes can also be transformed without re-recording the display + * list through the transform properties.</p> + * + * <p>A text editor might for instance store each paragraph into its own RenderNode. + * Thus when the user inserts or removes characters, only the display list of the + * affected paragraph needs to be recorded again.</p> + * + * <h3>Hardware acceleration</h3> + * <p>RenderNodes can be drawn using a {@link RecordingCanvas}. They are not + * supported in software. Always make sure that the {@link android.graphics.Canvas} + * you are using to render a display list is hardware accelerated using + * {@link android.graphics.Canvas#isHardwareAccelerated()}.</p> + * + * <h3>Creating a RenderNode</h3> + * <pre class="prettyprint"> + * RenderNode renderNode = new RenderNode("myRenderNode"); + * renderNode.setLeftTopRightBottom(0, 0, 50, 50); // Set the size to 50x50 + * RecordingCanvas canvas = renderNode.beginRecording(); + * try { + * // Draw with the canvas + * canvas.drawRect(...); + * } finally { + * renderNode.endRecording(); + * }</pre> + * + * <h3>Drawing a RenderNode in a View</h3> + * <pre class="prettyprint"> + * protected void onDraw(Canvas canvas) { + * if (canvas.isHardwareAccelerated()) { + * // Check that the RenderNode has a display list, re-recording it if it does not. + * if (!myRenderNode.hasDisplayList()) { + * updateDisplayList(myRenderNode); + * } + * // Draw the RenderNode into this canvas. + * canvas.drawRenderNode(myRenderNode); + * } + * }</pre> + * + * <h3>Releasing resources</h3> + * <p>This step is not mandatory but recommended if you want to release resources + * held by a display list as soon as possible. Most significantly any bitmaps it may contain.</p> + * <pre class="prettyprint"> + * // Discards the display list content allowing for any held resources to be released. + * // After calling this + * renderNode.discardDisplayList();</pre> + * + * + * <h3>Properties</h3> + * <p>In addition, a RenderNode offers several properties, such as + * {@link #setScaleX(float)} or {@link #setTranslationX(float)}, that can be used to affect all + * the drawing commands recorded within. For instance, these properties can be used + * to move around a large number of images without re-issuing all the individual + * <code>canvas.drawBitmap()</code> calls.</p> + * + * <pre class="prettyprint"> + * private void createDisplayList() { + * mRenderNode = new RenderNode("MyRenderNode"); + * mRenderNode.setLeftTopRightBottom(0, 0, width, height); + * RecordingCanvas canvas = mRenderNode.beginRecording(); + * try { + * for (Bitmap b : mBitmaps) { + * canvas.drawBitmap(b, 0.0f, 0.0f, null); + * canvas.translate(0.0f, b.getHeight()); + * } + * } finally { + * mRenderNode.endRecording(); + * } + * } + * + * protected void onDraw(Canvas canvas) { + * if (canvas.isHardwareAccelerated()) + * canvas.drawRenderNode(mRenderNode); + * } + * } + * + * private void moveContentBy(int x) { + * // This will move all the bitmaps recorded inside the display list + * // by x pixels to the right and redraw this view. All the commands + * // recorded in createDisplayList() won't be re-issued, only onDraw() + * // will be invoked and will execute very quickly + * mRenderNode.offsetLeftAndRight(x); + * invalidate(); + * }</pre> + * + * <p>A few of the properties may at first appear redundant, such as {@link #setElevation(float)} + * and {@link #setTranslationZ(float)}. The reason for these duplicates are to allow for a + * separation between static & transient usages. For example consider a button that raises from 2dp + * to 8dp when pressed. To achieve that an application may decide to setElevation(2dip), and then + * on press to animate setTranslationZ to 6dip. Combined this achieves the final desired 8dip + * value, but the animation need only concern itself with animating the lift from press without + * needing to know the initial starting value. {@link #setTranslationX(float)} and + * {@link #setTranslationY(float)} are similarly provided for animation uses despite the functional + * overlap with {@link #setPosition(Rect)}. + * + * <p>The RenderNode's transform matrix is computed at render time as follows: + * <pre class="prettyprint"> + * Matrix transform = new Matrix(); + * transform.setTranslate(renderNode.getTranslationX(), renderNode.getTranslationY()); + * transform.preRotate(renderNode.getRotationZ(), + * renderNode.getPivotX(), renderNode.getPivotY()); + * transform.preScale(renderNode.getScaleX(), renderNode.getScaleY(), + * renderNode.getPivotX(), renderNode.getPivotY());</pre> + * The current canvas transform matrix, which is translated to the RenderNode's position, + * is then multiplied by the RenderNode's transform matrix. Therefore the ordering of calling + * property setters does not affect the result. That is to say that: + * + * <pre class="prettyprint"> + * renderNode.setTranslationX(100); + * renderNode.setScaleX(100);</pre> + * + * is equivalent to: + * + * <pre class="prettyprint"> + * renderNode.setScaleX(100); + * renderNode.setTranslationX(100);</pre> + * + * <h3>Threading</h3> + * <p>RenderNode may be created and used on any thread but they are not thread-safe. Only + * a single thread may interact with a RenderNode at any given time. It is critical + * that the RenderNode is only used on the same thread it is drawn with. For example when using + * RenderNode with a custom View, then that RenderNode must only be used from the UI thread.</p> + * + * <h3>When to re-render</h3> + * <p>Many of the RenderNode mutation methods, such as {@link #setTranslationX(float)}, return + * a boolean indicating if the value actually changed or not. This is useful in detecting + * if a new frame should be rendered or not. A typical usage would look like: + * <pre class="prettyprint"> + * public void translateTo(int x, int y) { + * boolean needsUpdate = myRenderNode.setTranslationX(x); + * needsUpdate |= myRenderNode.setTranslationY(y); + * if (needsUpdate) { + * myOwningView.invalidate(); + * } + * }</pre> + * This is marginally faster than doing a more explicit up-front check if the value changed by + * comparing the desired value against {@link #getTranslationX()} as it minimizes JNI transitions. + * The actual mechanism of requesting a new frame to be rendered will depend on how this + * RenderNode is being drawn. If it's drawn to a containing View, as in the above snippet, + * then simply invalidating that View works. If instead the RenderNode is being drawn to a Canvas + * directly such as with {@link Surface#lockHardwareCanvas()} then a new frame needs to be drawn + * by calling {@link Surface#lockHardwareCanvas()}, re-drawing the root RenderNode or whatever + * top-level content is desired, and finally calling {@link Surface#unlockCanvasAndPost(Canvas)}. + * </p> + */ +public final class RenderNode { + + // Use a Holder to allow static initialization in the boot image. + private static class NoImagePreloadHolder { + public static final NativeAllocationRegistry sRegistry = + NativeAllocationRegistry.createMalloced( + RenderNode.class.getClassLoader(), nGetNativeFinalizer()); + } + + /** + * Not for general use; use only if you are ThreadedRenderer or RecordingCanvas. + * + * @hide + */ + public final long mNativeRenderNode; + private final AnimationHost mAnimationHost; + private RecordingCanvas mCurrentRecordingCanvas; + + // Will be null if not currently registered + @Nullable + private CompositePositionUpdateListener mCompositePositionUpdateListener; + + /** + * Creates a new RenderNode that can be used to record batches of + * drawing operations, and store / apply render properties when drawn. + * + * @param name The name of the RenderNode, used for debugging purpose. May be null. + */ + public RenderNode(@Nullable String name) { + this(name, null); + } + + private RenderNode(String name, AnimationHost animationHost) { + mNativeRenderNode = nCreate(name); + NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativeRenderNode); + mAnimationHost = animationHost; + } + + /** + * @see RenderNode#adopt(long) + */ + private RenderNode(long nativePtr) { + mNativeRenderNode = nativePtr; + NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativeRenderNode); + mAnimationHost = null; + } + + /** @hide */ + public static RenderNode create(String name, @Nullable AnimationHost animationHost) { + return new RenderNode(name, animationHost); + } + + /** + * Adopts an existing native render node. + * + * Note: This will *NOT* incRef() on the native object, however it will + * decRef() when it is destroyed. The caller should have already incRef'd it + * + * @hide + */ + public static RenderNode adopt(long nativePtr) { + return new RenderNode(nativePtr); + } + + /** + * Listens for RenderNode position updates for synchronous window movement. + * + * This is not suitable for generic position listening, it is only designed & intended + * for use by things which require external position events like SurfaceView, PopupWindow, etc.. + * + * @hide + */ + public interface PositionUpdateListener { + + /** + * Called by native by a Rendering Worker thread to update window position + * + * @hide + */ + void positionChanged(long frameNumber, int left, int top, int right, int bottom); + + /** + * Called by native on RenderThread to notify that the view is no longer in the + * draw tree. UI thread is blocked at this point. + * + * @hide + */ + void positionLost(long frameNumber); + + } + + private static final class CompositePositionUpdateListener implements PositionUpdateListener { + private final PositionUpdateListener[] mListeners; + private static final PositionUpdateListener[] sEmpty = new PositionUpdateListener[0]; + + CompositePositionUpdateListener(PositionUpdateListener... listeners) { + mListeners = listeners != null ? listeners : sEmpty; + } + + public CompositePositionUpdateListener with(PositionUpdateListener listener) { + return new CompositePositionUpdateListener( + ArrayUtils.appendElement(PositionUpdateListener.class, mListeners, listener)); + } + + public CompositePositionUpdateListener without(PositionUpdateListener listener) { + return new CompositePositionUpdateListener( + ArrayUtils.removeElement(PositionUpdateListener.class, mListeners, listener)); + } + + @Override + public void positionChanged(long frameNumber, int left, int top, int right, int bottom) { + for (PositionUpdateListener pul : mListeners) { + pul.positionChanged(frameNumber, left, top, right, bottom); + } + } + + @Override + public void positionLost(long frameNumber) { + for (PositionUpdateListener pul : mListeners) { + pul.positionLost(frameNumber); + } + } + } + + /** + * Enable callbacks for position changes. Call only from the UI thread or with + * external synchronization. + * + * @hide + */ + public void addPositionUpdateListener(@NonNull PositionUpdateListener listener) { + CompositePositionUpdateListener comp = mCompositePositionUpdateListener; + if (comp == null) { + comp = new CompositePositionUpdateListener(listener); + } else { + comp = comp.with(listener); + } + mCompositePositionUpdateListener = comp; + nRequestPositionUpdates(mNativeRenderNode, comp); + } + + /** + * Disable a callback for position changes. Call only from the UI thread or with + * external synchronization. + * + * @param listener Callback to remove + * @hide + */ + public void removePositionUpdateListener(@NonNull PositionUpdateListener listener) { + CompositePositionUpdateListener comp = mCompositePositionUpdateListener; + if (comp != null) { + comp = comp.without(listener); + mCompositePositionUpdateListener = comp; + nRequestPositionUpdates(mNativeRenderNode, comp); + } + } + + /** + * Starts recording a display list for the render node. All + * operations performed on the returned canvas are recorded and + * stored in this display list. + * + * {@link #endRecording()} must be called when the recording is finished in order to apply + * the updated display list. Failing to call {@link #endRecording()} will result in an + * {@link IllegalStateException} if {@link #beginRecording(int, int)} is called again. + * + * @param width The width of the recording viewport. This will not alter the width of the + * RenderNode itself, that must be set with {@link #setPosition(Rect)}. + * @param height The height of the recording viewport. This will not alter the height of the + * RenderNode itself, that must be set with {@link #setPosition(Rect)}. + * @return A canvas to record drawing operations. + * @throws IllegalStateException If a recording is already in progress. That is, the previous + * call to {@link #beginRecording(int, int)} did not call {@link #endRecording()}. + * @see #endRecording() + * @see #hasDisplayList() + */ + public @NonNull RecordingCanvas beginRecording(int width, int height) { + if (mCurrentRecordingCanvas != null) { + throw new IllegalStateException( + "Recording currently in progress - missing #endRecording() call?"); + } + mCurrentRecordingCanvas = RecordingCanvas.obtain(this, width, height); + return mCurrentRecordingCanvas; + } + + /** + * Same as {@link #beginRecording(int, int)} with the width & height set + * to the RenderNode's own width & height. The RenderNode's width & height may be set + * with {@link #setPosition(int, int, int, int)}. + * + * @return A canvas to record drawing operations. + * @throws IllegalStateException If a recording is already in progress. That is, the previous + * call to {@link #beginRecording(int, int)} did not call {@link #endRecording()}. + * @see #endRecording() + * @see #hasDisplayList() + */ + public @NonNull RecordingCanvas beginRecording() { + return beginRecording(nGetWidth(mNativeRenderNode), nGetHeight(mNativeRenderNode)); + } + + /** + * ` + * Ends the recording for this display list. Calling this method marks + * the display list valid and {@link #hasDisplayList()} will return true. + * + * @see #beginRecording(int, int) + * @see #hasDisplayList() + */ + public void endRecording() { + if (mCurrentRecordingCanvas == null) { + throw new IllegalStateException( + "No recording in progress, forgot to call #beginRecording()?"); + } + RecordingCanvas canvas = mCurrentRecordingCanvas; + mCurrentRecordingCanvas = null; + long displayList = canvas.finishRecording(); + nSetDisplayList(mNativeRenderNode, displayList); + canvas.recycle(); + } + + /** + * @hide + * @deprecated use {@link #beginRecording(int, int)} instead + */ + @Deprecated + public RecordingCanvas start(int width, int height) { + return beginRecording(width, height); + } + + /** + * @hide + * @deprecated use {@link #endRecording()} instead + */ + @Deprecated + public void end(RecordingCanvas canvas) { + if (canvas != mCurrentRecordingCanvas) { + throw new IllegalArgumentException("Wrong canvas"); + } + endRecording(); + } + + /** + * Reset native resources. This is called when cleaning up the state of display lists + * during destruction of hardware resources, to ensure that we do not hold onto + * obsolete resources after related resources are gone. + */ + public void discardDisplayList() { + nSetDisplayList(mNativeRenderNode, 0); + } + + /** + * Returns whether the RenderNode has a display list. If this returns false, the RenderNode + * should be re-recorded with {@link #beginRecording()} and {@link #endRecording()}. + * + * A RenderNode without a display list may still be drawn, however it will have no impact + * on the rendering content until its display list is updated. + * + * When a RenderNode is no longer drawn by anything the system may automatically + * invoke {@link #discardDisplayList()}. It is therefore important to ensure that + * {@link #hasDisplayList()} is true on a RenderNode prior to drawing it. + * + * See {@link #discardDisplayList()} + * + * @return boolean true if this RenderNode has a display list, false otherwise. + */ + public boolean hasDisplayList() { + return nIsValid(mNativeRenderNode); + } + + /////////////////////////////////////////////////////////////////////////// + // Matrix manipulation + /////////////////////////////////////////////////////////////////////////// + + /** + * Whether or not the RenderNode has an identity transform. This is a faster + * way to do the otherwise equivalent {@link #getMatrix(Matrix)} {@link Matrix#isIdentity()} + * as it doesn't require copying the Matrix first, thus minimizing overhead. + * + * @return true if the RenderNode has an identity transform, false otherwise + */ + public boolean hasIdentityMatrix() { + return nHasIdentityMatrix(mNativeRenderNode); + } + + /** + * Gets the current transform matrix + * + * @param outMatrix The matrix to store the transform of the RenderNode + */ + public void getMatrix(@NonNull Matrix outMatrix) { + nGetTransformMatrix(mNativeRenderNode, outMatrix.native_instance); + } + + /** + * Gets the current transform inverted. This is a faster way to do the otherwise + * equivalent {@link #getMatrix(Matrix)} followed by {@link Matrix#invert(Matrix)} + * + * @param outMatrix The matrix to store the inverse transform of the RenderNode + */ + public void getInverseMatrix(@NonNull Matrix outMatrix) { + nGetInverseTransformMatrix(mNativeRenderNode, outMatrix.native_instance); + } + + /////////////////////////////////////////////////////////////////////////// + // RenderProperty Setters + /////////////////////////////////////////////////////////////////////////// + + /** + * @hide + * @deprecated use {@link #setUseCompositingLayer(boolean, Paint)} instead + */ + @Deprecated + public boolean setLayerType(int layerType) { + return nSetLayerType(mNativeRenderNode, layerType); + } + + /** + * @hide + * @deprecated use {@link #setUseCompositingLayer(boolean, Paint)} instead + */ + @Deprecated + public boolean setLayerPaint(@Nullable Paint paint) { + return nSetLayerPaint(mNativeRenderNode, paint != null ? paint.getNativeInstance() : 0); + } + + /** + * Controls whether or not to force this RenderNode to render to an intermediate buffer. + * Internally RenderNode will already promote itself to a composition layer if it's useful + * for performance or required for the current combination of {@link #setAlpha(float)} and + * {@link #setHasOverlappingRendering(boolean)}. + * + * <p>The usage of this is instead to allow for either overriding of the internal behavior + * if it's measured to be necessary for the particular rendering content in question or, more + * usefully, to add a composition effect to the RenderNode via the optional paint parameter. + * + * <p>Note: When a RenderNode is using a compositing layer it will also result in + * clipToBounds=true behavior. + * + * @param forceToLayer if true this forces the RenderNode to use an intermediate buffer. + * Default & generally recommended value is false. + * @param paint The blend mode, alpha, and ColorFilter to apply to the compositing layer. + * Only applies if forceToLayer is true. The paint's alpha is multiplied + * with {@link #getAlpha()} to resolve the final alpha of the RenderNode. + * If null then no additional composition effects are applied on top of the + * composition layer. + * @return True if the value changed, false if the new value was the same as the previous value. + */ + public boolean setUseCompositingLayer(boolean forceToLayer, @Nullable Paint paint) { + boolean didChange = nSetLayerType(mNativeRenderNode, forceToLayer ? 2 : 0); + didChange |= nSetLayerPaint(mNativeRenderNode, + paint != null ? paint.getNativeInstance() : 0); + return didChange; + } + + /** + * Gets whether or not a compositing layer is forced to be used. The default & recommended + * is false, as it is typically faster to avoid using compositing layers. + * See {@link #setUseCompositingLayer(boolean, Paint)}. + * + * @return true if a compositing layer is forced, false otherwise + */ + public boolean getUseCompositingLayer() { + return nGetLayerType(mNativeRenderNode) != 0; + } + + /** + * Sets an additional clip on the RenderNode. If null, the extra clip is removed from the + * RenderNode. If non-null, the RenderNode will be clipped to this rect. In addition if + * {@link #setClipToBounds(boolean)} is true, then the RenderNode will be clipped to the + * intersection of this rectangle and the bounds of the render node, which is set with + * {@link #setPosition(Rect)}. + * + * <p>This is equivalent to do a {@link Canvas#clipRect(Rect)} at the start of this + * RenderNode's display list. However, as this is a property of the RenderNode instead + * of part of the display list it can be more easily animated for transient additional + * clipping. An example usage of this would be the {@link android.transition.ChangeBounds} + * transition animation with the resizeClip=true option. + * + * @param rect the bounds to clip to. If null, the additional clip is removed. + * @return True if the value changed, false if the new value was the same as the previous value. + */ + public boolean setClipRect(@Nullable Rect rect) { + if (rect == null) { + return nSetClipBoundsEmpty(mNativeRenderNode); + } else { + return nSetClipBounds(mNativeRenderNode, rect.left, rect.top, rect.right, rect.bottom); + } + } + + /** + * Set whether the Render node should clip itself to its bounds. This defaults to true, + * and is useful to the renderer in enable quick-rejection of chunks of the tree as well as + * better partial invalidation support. Clipping can be further restricted or controlled + * through the combination of this property as well as {@link #setClipRect(Rect)}, which + * allows for a different clipping rectangle to be used in addition to or instead of the + * {@link #setPosition(int, int, int, int)} or the RenderNode. + * + * @param clipToBounds true if the display list should clip to its bounds, false otherwise. + * @return True if the value changed, false if the new value was the same as the previous value. + */ + public boolean setClipToBounds(boolean clipToBounds) { + return nSetClipToBounds(mNativeRenderNode, clipToBounds); + } + + /** + * Returns whether or not the RenderNode is clipping to its bounds. See + * {@link #setClipToBounds(boolean)} and {@link #setPosition(int, int, int, int)} + * + * @return true if the render node clips to its bounds, false otherwise. + */ + public boolean getClipToBounds() { + return nGetClipToBounds(mNativeRenderNode); + } + + /** + * <p>Sets whether the RenderNode should be drawn immediately after the + * closest ancestor RenderNode containing a projection receiver. + * + * <p>The default is false, and the rendering of this node happens in the typical draw order. + * + * <p>If true, then at rendering time this rendernode will not be drawn in order with the + * {@link Canvas#drawRenderNode(RenderNode)} command that drew this RenderNode, but instead + * it will be re-positioned in the RenderNode tree to be drawn on the closet ancestor with a + * child rendernode that has {@link #setProjectionReceiver(boolean)} as true. + * + * <p>The typical usage of this is to allow a child RenderNode to draw on a parent's background, + * such as the platform's usage with {@link android.graphics.drawable.RippleDrawable}. Consider + * the following structure, built out of which RenderNode called drawRenderNode on a different + * RenderNode: + * + * <pre> + * +-------------+ + * |RenderNode: P| + * +-+----------++ + * | | + * v v + * +-------+-----+ +-+--------------+ + * |RenderNode: C| |RenderNode: P'BG| + * +-------+-----+ +----------------+ + * | + * | + * +--------+-------+ + * |RenderNode: C'BG| + * +----------------+ + * </pre> + * + * If P'BG is a projection receiver, and C'BG is set to project backwards then C'BG will + * behave as if it was drawn directly by P'BG instead of by C. This includes inheriting P'BG's + * clip instead of C's clip. + * + * @param shouldProject true if the display list should be projected onto a + * containing volume. Default is false. + * @return True if the value changed, false if the new value was the same as the previous value. + */ + public boolean setProjectBackwards(boolean shouldProject) { + return nSetProjectBackwards(mNativeRenderNode, shouldProject); + } + + /** + * Sets whether the RenderNode is a projection receiver. If true then this RenderNode's parent + * should draw any descendant RenderNodes with ProjectBackwards=true directly on top of it. + * Default value is false. See + * {@link #setProjectBackwards(boolean)} for a description of what this entails. + * + * @param shouldRecieve True if this RenderNode is a projection receiver, false otherwise. + * Default is false. + * @return True if the value changed, false if the new value was the same as the previous value. + */ + public boolean setProjectionReceiver(boolean shouldRecieve) { + return nSetProjectionReceiver(mNativeRenderNode, shouldRecieve); + } + + /** + * Sets the outline, defining the shape that casts a shadow, and the path to + * be clipped if setClipToOutline is set. + * + * This will make a copy of the provided {@link Outline}, so any future modifications + * to the outline will need to call {@link #setOutline(Outline)} with the modified + * outline for those changes to be applied. + * + * @param outline The outline to use for this RenderNode. + * @return True if the value changed, false if the new value was the same as the previous value. + */ + public boolean setOutline(@Nullable Outline outline) { + if (outline == null) { + return nSetOutlineNone(mNativeRenderNode); + } + + switch (outline.mMode) { + case Outline.MODE_EMPTY: + return nSetOutlineEmpty(mNativeRenderNode); + case Outline.MODE_ROUND_RECT: + return nSetOutlineRoundRect(mNativeRenderNode, + outline.mRect.left, outline.mRect.top, + outline.mRect.right, outline.mRect.bottom, + outline.mRadius, outline.mAlpha); + case Outline.MODE_CONVEX_PATH: + return nSetOutlineConvexPath(mNativeRenderNode, outline.mPath.mNativePath, + outline.mAlpha); + } + + throw new IllegalArgumentException("Unrecognized outline?"); + } + + /** + * Checks if the RenderNode has a shadow. That is, if the combination of {@link #getElevation()} + * and {@link #getTranslationZ()} is greater than zero, there is an {@link Outline} set with + * a valid shadow caster path, and the provided outline has a non-zero + * {@link Outline#getAlpha()}. + * + * @return True if this RenderNode has a shadow, false otherwise + */ + public boolean hasShadow() { + return nHasShadow(mNativeRenderNode); + } + + /** + * Sets the color of the spot shadow that is drawn when the RenderNode has a positive Z or + * elevation value and is drawn inside of a {@link Canvas#enableZ()} section. + * <p> + * By default the shadow color is black. Generally, this color will be opaque so the intensity + * of the shadow is consistent between different RenderNodes with different colors. + * <p> + * The opacity of the final spot shadow is a function of the shadow caster height, the + * alpha channel of the outlineSpotShadowColor (typically opaque), and the + * {@link android.R.attr#spotShadowAlpha} theme attribute + * + * @param color The color this RenderNode will cast for its elevation spot shadow. + * @return True if the value changed, false if the new value was the same as the previous value. + */ + public boolean setSpotShadowColor(@ColorInt int color) { + return nSetSpotShadowColor(mNativeRenderNode, color); + } + + /** + * @return The shadow color set by {@link #setSpotShadowColor(int)}, or black if nothing + * was set + */ + public @ColorInt int getSpotShadowColor() { + return nGetSpotShadowColor(mNativeRenderNode); + } + + /** + * Sets the color of the ambient shadow that is drawn when the RenderNode has a positive Z or + * elevation value and is drawn inside of a {@link Canvas#enableZ()} section. + * <p> + * By default the shadow color is black. Generally, this color will be opaque so the intensity + * of the shadow is consistent between different RenderNodes with different colors. + * <p> + * The opacity of the final ambient shadow is a function of the shadow caster height, the + * alpha channel of the outlineAmbientShadowColor (typically opaque), and the + * {@link android.R.attr#ambientShadowAlpha} theme attribute. + * + * @param color The color this RenderNode will cast for its elevation shadow. + * @return True if the value changed, false if the new value was the same as the previous value. + */ + public boolean setAmbientShadowColor(@ColorInt int color) { + return nSetAmbientShadowColor(mNativeRenderNode, color); + } + + /** + * @return The shadow color set by {@link #setAmbientShadowColor(int)}, or black if + * nothing was set + */ + public @ColorInt int getAmbientShadowColor() { + return nGetAmbientShadowColor(mNativeRenderNode); + } + + /** + * Enables or disables clipping to the outline. + * + * @param clipToOutline true if clipping to the outline. + * @return True if the clipToOutline value changed, false if previous value matched the new + * value. + */ + public boolean setClipToOutline(boolean clipToOutline) { + return nSetClipToOutline(mNativeRenderNode, clipToOutline); + } + + /** + * See {@link #setClipToOutline(boolean)} + * + * @return True if this RenderNode clips to its outline, false otherwise + */ + public boolean getClipToOutline() { + return nGetClipToOutline(mNativeRenderNode); + } + + /** + * Controls the RenderNode's circular reveal clip. + * + * @hide + */ + public boolean setRevealClip(boolean shouldClip, + float x, float y, float radius) { + return nSetRevealClip(mNativeRenderNode, shouldClip, x, y, radius); + } + + /** + * Set the static matrix on the display list. The specified matrix is combined with other + * transforms (such as {@link #setScaleX(float)}, {@link #setRotationZ(float)}, etc.) + * + * @param matrix A transform matrix to apply to this display list + * @hide TODO Do we want this? + */ + public boolean setStaticMatrix(Matrix matrix) { + return nSetStaticMatrix(mNativeRenderNode, matrix.native_instance); + } + + /** + * Set the Animation matrix on the display list. This matrix exists if an Animation is + * currently playing on a View, and is set on the display list during at draw() time. When + * the Animation finishes, the matrix should be cleared by sending <code>null</code> + * for the matrix parameter. + * + * @param matrix The matrix, null indicates that the matrix should be cleared. + * @see #getAnimationMatrix() + * + * @hide TODO Do we want this? + */ + public boolean setAnimationMatrix(@Nullable Matrix matrix) { + return nSetAnimationMatrix(mNativeRenderNode, + (matrix != null) ? matrix.native_instance : 0); + } + + /** + * Returns the previously set Animation matrix. This matrix exists if an Animation is + * currently playing on a View, and is set on the display list during at draw() time. + * Returns <code>null</code> when there is no transformation provided by + * {@link #setAnimationMatrix(Matrix)}. + * + * @return the current Animation matrix. + * @see #setAnimationMatrix(Matrix) + * + * @hide + */ + @Nullable + public Matrix getAnimationMatrix() { + Matrix output = new Matrix(); + if (nGetAnimationMatrix(mNativeRenderNode, output.native_instance)) { + return output; + } else { + return null; + } + } + + /** + * Sets the translucency level for the display list. + * + * @param alpha The translucency of the display list, must be a value between 0.0f and 1.0f + * @see View#setAlpha(float) + * @see #getAlpha() + * @return True if the value changed, false if the new value was the same as the previous value. + */ + public boolean setAlpha(float alpha) { + return nSetAlpha(mNativeRenderNode, alpha); + } + + /** + * Returns the translucency level of this display list. + * + * @return A value between 0.0f and 1.0f + * @see #setAlpha(float) + */ + public float getAlpha() { + return nGetAlpha(mNativeRenderNode); + } + + /** + * Sets whether the display list renders content which overlaps. Non-overlapping rendering + * can use a fast path for alpha that avoids rendering to an offscreen buffer. By default + * display lists consider they do not have overlapping content. + * + * @param hasOverlappingRendering False if the content is guaranteed to be non-overlapping, + * true otherwise. + * @see android.view.View#hasOverlappingRendering() + * @see #hasOverlappingRendering() + */ + public boolean setHasOverlappingRendering(boolean hasOverlappingRendering) { + return nSetHasOverlappingRendering(mNativeRenderNode, hasOverlappingRendering); + } + + /** @hide */ + @IntDef({USAGE_BACKGROUND}) + @Retention(RetentionPolicy.SOURCE) + public @interface UsageHint { + } + + /** + * The default usage hint + * + * @hide + */ + public static final int USAGE_UNKNOWN = 0; + + /** + * Usage is background content + * + * @hide + */ + public static final int USAGE_BACKGROUND = 1; + + /** + * Provides a hint on what this RenderNode's display list content contains. This hint is used + * for automatic content transforms to improve accessibility or similar. + * + * @hide + */ + public void setUsageHint(@UsageHint int usageHint) { + nSetUsageHint(mNativeRenderNode, usageHint); + } + + /** + * Indicates whether the content of this display list overlaps. + * + * @return True if this display list renders content which overlaps, false otherwise. + * @see #setHasOverlappingRendering(boolean) + */ + public boolean hasOverlappingRendering() { + return nHasOverlappingRendering(mNativeRenderNode); + } + + /** + * Sets the base elevation of this RenderNode in pixels + * + * @param lift the elevation in pixels + * @return True if the value changed, false if the new value was the same as the previous value. + */ + public boolean setElevation(float lift) { + return nSetElevation(mNativeRenderNode, lift); + } + + /** + * See {@link #setElevation(float)} + * + * @return The RenderNode's current elevation + */ + public float getElevation() { + return nGetElevation(mNativeRenderNode); + } + + /** + * Sets the translation value for the display list on the X axis. + * + * @param translationX The X axis translation value of the display list, in pixels + * @see View#setTranslationX(float) + * @see #getTranslationX() + * @return True if the value changed, false if the new value was the same as the previous value. + */ + public boolean setTranslationX(float translationX) { + return nSetTranslationX(mNativeRenderNode, translationX); + } + + /** + * Returns the translation value for this display list on the X axis, in pixels. + * + * @see #setTranslationX(float) + */ + public float getTranslationX() { + return nGetTranslationX(mNativeRenderNode); + } + + /** + * Sets the translation value for the display list on the Y axis. + * + * @param translationY The Y axis translation value of the display list, in pixels + * @see View#setTranslationY(float) + * @see #getTranslationY() + * @return True if the value changed, false if the new value was the same as the previous value. + */ + public boolean setTranslationY(float translationY) { + return nSetTranslationY(mNativeRenderNode, translationY); + } + + /** + * Returns the translation value for this display list on the Y axis, in pixels. + * + * @see #setTranslationY(float) + */ + public float getTranslationY() { + return nGetTranslationY(mNativeRenderNode); + } + + /** + * Sets the translation value for the display list on the Z axis. + * + * @see View#setTranslationZ(float) + * @see #getTranslationZ() + * @return True if the value changed, false if the new value was the same as the previous value. + */ + public boolean setTranslationZ(float translationZ) { + return nSetTranslationZ(mNativeRenderNode, translationZ); + } + + /** + * Returns the translation value for this display list on the Z axis. + * + * @see #setTranslationZ(float) + */ + public float getTranslationZ() { + return nGetTranslationZ(mNativeRenderNode); + } + + /** + * Sets the rotation value for the display list around the Z axis. + * + * @param rotation The rotation value of the display list, in degrees + * @see View#setRotationZ(float) + * @see #getRotationZ() + * @return True if the value changed, false if the new value was the same as the previous value. + */ + public boolean setRotationZ(float rotation) { + return nSetRotation(mNativeRenderNode, rotation); + } + + /** + * Returns the rotation value for this display list around the Z axis, in degrees. + * + * @see #setRotationZ(float) + */ + public float getRotationZ() { + return nGetRotation(mNativeRenderNode); + } + + /** + * Sets the rotation value for the display list around the X axis. + * + * @param rotationX The rotation value of the display list, in degrees + * @see View#setRotationX(float) + * @see #getRotationX() + * @return True if the value changed, false if the new value was the same as the previous value. + */ + public boolean setRotationX(float rotationX) { + return nSetRotationX(mNativeRenderNode, rotationX); + } + + /** + * Returns the rotation value for this display list around the X axis, in degrees. + * + * @see #setRotationX(float) + */ + public float getRotationX() { + return nGetRotationX(mNativeRenderNode); + } + + /** + * Sets the rotation value for the display list around the Y axis. + * + * @param rotationY The rotation value of the display list, in degrees + * @see View#setRotationY(float) + * @see #getRotationY() + * @return True if the value changed, false if the new value was the same as the previous value. + */ + public boolean setRotationY(float rotationY) { + return nSetRotationY(mNativeRenderNode, rotationY); + } + + /** + * Returns the rotation value for this display list around the Y axis, in degrees. + * + * @see #setRotationY(float) + */ + public float getRotationY() { + return nGetRotationY(mNativeRenderNode); + } + + /** + * Sets the scale value for the display list on the X axis. + * + * @param scaleX The scale value of the display list + * @see View#setScaleX(float) + * @see #getScaleX() + * @return True if the value changed, false if the new value was the same as the previous value. + */ + public boolean setScaleX(float scaleX) { + return nSetScaleX(mNativeRenderNode, scaleX); + } + + /** + * Returns the scale value for this display list on the X axis. + * + * @see #setScaleX(float) + */ + public float getScaleX() { + return nGetScaleX(mNativeRenderNode); + } + + /** + * Sets the scale value for the display list on the Y axis. + * + * @param scaleY The scale value of the display list + * @see View#setScaleY(float) + * @see #getScaleY() + * @return True if the value changed, false if the new value was the same as the previous value. + */ + public boolean setScaleY(float scaleY) { + return nSetScaleY(mNativeRenderNode, scaleY); + } + + /** + * Returns the scale value for this display list on the Y axis. + * + * @see #setScaleY(float) + */ + public float getScaleY() { + return nGetScaleY(mNativeRenderNode); + } + + /** + * Sets the pivot value for the display list on the X axis + * + * @param pivotX The pivot value of the display list on the X axis, in pixels + * @see View#setPivotX(float) + * @see #getPivotX() + * @return True if the value changed, false if the new value was the same as the previous value. + */ + public boolean setPivotX(float pivotX) { + return nSetPivotX(mNativeRenderNode, pivotX); + } + + /** + * Returns the pivot value for this display list on the X axis, in pixels. + * + * @see #setPivotX(float) + */ + public float getPivotX() { + return nGetPivotX(mNativeRenderNode); + } + + /** + * Sets the pivot value for the display list on the Y axis + * + * @param pivotY The pivot value of the display list on the Y axis, in pixels + * @see View#setPivotY(float) + * @see #getPivotY() + * @return True if the value changed, false if the new value was the same as the previous value. + */ + public boolean setPivotY(float pivotY) { + return nSetPivotY(mNativeRenderNode, pivotY); + } + + /** + * Returns the pivot value for this display list on the Y axis, in pixels. + * + * @see #setPivotY(float) + */ + public float getPivotY() { + return nGetPivotY(mNativeRenderNode); + } + + /** + * @return Whether or not a pivot was explicitly set with {@link #setPivotX(float)} or + * {@link #setPivotY(float)}. If no pivot has been set then the pivot will be the center + * of the RenderNode. + */ + public boolean isPivotExplicitlySet() { + return nIsPivotExplicitlySet(mNativeRenderNode); + } + + /** + * Clears any pivot previously set by a call to {@link #setPivotX(float)} or + * {@link #setPivotY(float)}. After calling this {@link #isPivotExplicitlySet()} will be false + * and the pivot used for rotation will return to default of being centered on the view. + * + * @return True if the value changed, false if the new value was the same as the previous value. + */ + public boolean resetPivot() { + return nResetPivot(mNativeRenderNode); + } + + /** + * <p>Sets the distance along the Z axis (orthogonal to the X/Y plane on which + * RenderNodes are drawn) from the camera to this RenderNode. The camera's distance + * affects 3D transformations, for instance rotations around the X and Y + * axis. If the rotationX or rotationY properties are changed and this view is + * large (more than half the size of the screen), it is recommended to always + * use a camera distance that's greater than the height (X axis rotation) or + * the width (Y axis rotation) of this view.</p> + * + * <p>The distance of the camera from the drawing plane can have an affect on the + * perspective distortion of the RenderNode when it is rotated around the x or y axis. + * For example, a large distance will result in a large viewing angle, and there + * will not be much perspective distortion of the view as it rotates. A short + * distance may cause much more perspective distortion upon rotation, and can + * also result in some drawing artifacts if the rotated view ends up partially + * behind the camera (which is why the recommendation is to use a distance at + * least as far as the size of the view, if the view is to be rotated.)</p> + * + * <p>The distance is expressed in pixels and must always be positive</p> + * + * @param distance The distance in pixels, must always be positive + * @see #setRotationX(float) + * @see #setRotationY(float) + * @return True if the value changed, false if the new value was the same as the previous value. + */ + public boolean setCameraDistance( + @FloatRange(from = 0.0f, to = Float.MAX_VALUE) float distance) { + if (!Float.isFinite(distance) || distance < 0.0f) { + throw new IllegalArgumentException("distance must be finite & positive, given=" + + distance); + } + // Native actually wants this to be negative not positive, so we flip it. + return nSetCameraDistance(mNativeRenderNode, -distance); + } + + /** + * Returns the distance in Z of the camera for this RenderNode + * + * @return the distance along the Z axis in pixels. + * @see #setCameraDistance(float) + */ + public @FloatRange(from = 0.0f, to = Float.MAX_VALUE) float getCameraDistance() { + return -nGetCameraDistance(mNativeRenderNode); + } + + /** + * Sets the left position for the RenderNode. + * + * @param left The left position, in pixels, of the RenderNode + * @return true if the value changed, false otherwise + * @hide + */ + public boolean setLeft(int left) { + return nSetLeft(mNativeRenderNode, left); + } + + /** + * Sets the top position for the RenderNode. + * + * @param top The top position, in pixels, of the RenderNode + * @return true if the value changed, false otherwise. + * @hide + */ + public boolean setTop(int top) { + return nSetTop(mNativeRenderNode, top); + } + + /** + * Sets the right position for the RenderNode. + * + * @param right The right position, in pixels, of the RenderNode + * @return true if the value changed, false otherwise. + * @hide + */ + public boolean setRight(int right) { + return nSetRight(mNativeRenderNode, right); + } + + /** + * Sets the bottom position for the RenderNode. + * + * @param bottom The bottom position, in pixels, of the RenderNode + * @return true if the value changed, false otherwise. + * @hide + */ + public boolean setBottom(int bottom) { + return nSetBottom(mNativeRenderNode, bottom); + } + + /** + * Gets the left position for the RenderNode. + * + * @return the left position in pixels + */ + public int getLeft() { + return nGetLeft(mNativeRenderNode); + } + + /** + * Gets the top position for the RenderNode. + * + * @return the top position in pixels + */ + public int getTop() { + return nGetTop(mNativeRenderNode); + } + + /** + * Gets the right position for the RenderNode. + * + * @return the right position in pixels + */ + public int getRight() { + return nGetRight(mNativeRenderNode); + } + + /** + * Gets the bottom position for the RenderNode. + * + * @return the bottom position in pixels + */ + public int getBottom() { + return nGetBottom(mNativeRenderNode); + } + + /** + * Gets the width of the RenderNode, which is the right - left. + * + * @return the width of the RenderNode + */ + public int getWidth() { + return nGetWidth(mNativeRenderNode); + } + + /** + * Gets the height of the RenderNode, which is the bottom - top. + * + * @return the height of the RenderNode + */ + public int getHeight() { + return nGetHeight(mNativeRenderNode); + } + + /** + * Sets the left, top, right, and bottom of the RenderNode. + * + * @param left The left position of the RenderNode, in pixels + * @param top The top position of the RenderNode, in pixels + * @param right The right position of the RenderNode, in pixels + * @param bottom The bottom position of the RenderNode, in pixels + * @return true if any values changed, false otherwise. + * @hide + */ + public boolean setLeftTopRightBottom(int left, int top, int right, int bottom) { + return nSetLeftTopRightBottom(mNativeRenderNode, left, top, right, bottom); + } + + /** + * Sets the position of the RenderNode. + * + * @param left The left position of the RenderNode, in pixels + * @param top The top position of the RenderNode, in pixels + * @param right The right position of the RenderNode, in pixels + * @param bottom The bottom position of the RenderNode, in pixels + * @return True if the value changed, false if the new value was the same as the previous value. + */ + public boolean setPosition(int left, int top, int right, int bottom) { + return nSetLeftTopRightBottom(mNativeRenderNode, left, top, right, bottom); + } + + /** + * Sets the position of the RenderNode. + * + * @param position The position rectangle in pixels + * @return True if the value changed, false if the new value was the same as the previous value. + */ + public boolean setPosition(@NonNull Rect position) { + return nSetLeftTopRightBottom(mNativeRenderNode, + position.left, position.top, position.right, position.bottom); + } + + /** + * Offsets the left and right positions for the RenderNode + * + * @param offset The amount that the left and right positions are offset in pixels + * @return True if the value changed, false if the new value was the same as the previous value. + */ + public boolean offsetLeftAndRight(int offset) { + return nOffsetLeftAndRight(mNativeRenderNode, offset); + } + + /** + * Offsets the top and bottom values for the RenderNode + * + * @param offset The amount that the left and right positions are offset in pixels + * @return True if the value changed, false if the new value was the same as the previous value. + */ + public boolean offsetTopAndBottom(int offset) { + return nOffsetTopAndBottom(mNativeRenderNode, offset); + } + + /** + * Outputs the RenderNode to the log. This method exists for use by + * tools to output display lists for selected nodes to the log. + * + * @hide TODO: Expose? Should the shape of this be different than forced dump to logcat? + */ + public void output() { + nOutput(mNativeRenderNode); + } + + /** + * Gets the approximate memory usage of the RenderNode for debug purposes. Does not include + * the memory usage of any child RenderNodes nor any bitmaps, only the memory usage of + * this RenderNode and any data it owns. + * + * @return Approximate memory usage in bytes. + */ + public @BytesLong long computeApproximateMemoryUsage() { + return nGetDebugSize(mNativeRenderNode); + } + + /** + * Sets whether or not to allow force dark to apply to this RenderNode. + * + * Setting this to false will disable the auto-dark feature on everything this RenderNode + * draws, including any descendants. + * + * Setting this to true will allow this RenderNode to be automatically made dark, however + * a value of 'true' will not override any 'false' value in its parent chain nor will + * it prevent any 'false' in any of its children. + * + * @param allow Whether or not to allow force dark. + * @return True if the value changed, false if the new value was the same as the previous value. + */ + public boolean setForceDarkAllowed(boolean allow) { + return nSetAllowForceDark(mNativeRenderNode, allow); + } + + /** + * See {@link #setForceDarkAllowed(boolean)} + * + * @return true if force dark is allowed (default), false if it is disabled + */ + public boolean isForceDarkAllowed() { + return nGetAllowForceDark(mNativeRenderNode); + } + + /** + * Returns the unique ID that identifies this RenderNode. This ID is unique for the + * lifetime of the process. IDs are reset on process death, and are unique only within + * the process. + * + * This ID is intended to be used with debugging tools to associate a particular + * RenderNode across different debug dumping & inspection tools. For example + * a View layout inspector should include the unique ID for any RenderNodes that it owns + * to associate the drawing content with the layout content. + * + * @return the unique ID for this RenderNode + */ + public long getUniqueId() { + return nGetUniqueId(mNativeRenderNode); + } + + /////////////////////////////////////////////////////////////////////////// + // Animations + /////////////////////////////////////////////////////////////////////////// + + /** + * TODO: Figure out if this can be eliminated/refactored away + * + * For now this interface exists to de-couple RenderNode from anything View-specific in a + * bit of a kludge. + * + * @hide + */ + public interface AnimationHost { + /** @hide */ + void registerAnimatingRenderNode(RenderNode animator); + + /** @hide */ + void registerVectorDrawableAnimator(NativeVectorDrawableAnimator animator); + + /** @hide */ + boolean isAttached(); + } + + /** @hide */ + public void addAnimator(RenderNodeAnimator animator) { + if (!isAttached()) { + throw new IllegalStateException("Cannot start this animator on a detached view!"); + } + nAddAnimator(mNativeRenderNode, animator.getNativeAnimator()); + mAnimationHost.registerAnimatingRenderNode(this); + } + + /** @hide */ + public boolean isAttached() { + return mAnimationHost != null && mAnimationHost.isAttached(); + } + + /** @hide */ + public void registerVectorDrawableAnimator(NativeVectorDrawableAnimator animatorSet) { + if (!isAttached()) { + throw new IllegalStateException("Cannot start this animator on a detached view!"); + } + mAnimationHost.registerVectorDrawableAnimator(animatorSet); + } + + /** @hide */ + public void endAllAnimators() { + nEndAllAnimators(mNativeRenderNode); + } + + /////////////////////////////////////////////////////////////////////////// + // Regular JNI methods + /////////////////////////////////////////////////////////////////////////// + + private static native long nCreate(String name); + + private static native long nGetNativeFinalizer(); + + private static native void nOutput(long renderNode); + + private static native int nGetDebugSize(long renderNode); + + private static native void nRequestPositionUpdates(long renderNode, + PositionUpdateListener callback); + + // Animations + + private static native void nAddAnimator(long renderNode, long animatorPtr); + + private static native void nEndAllAnimators(long renderNode); + + + /////////////////////////////////////////////////////////////////////////// + // @FastNative methods + /////////////////////////////////////////////////////////////////////////// + + @FastNative + private static native void nSetDisplayList(long renderNode, long newData); + + + /////////////////////////////////////////////////////////////////////////// + // @CriticalNative methods + /////////////////////////////////////////////////////////////////////////// + + @CriticalNative + private static native boolean nIsValid(long renderNode); + + // Matrix + + @CriticalNative + private static native void nGetTransformMatrix(long renderNode, long nativeMatrix); + + @CriticalNative + private static native void nGetInverseTransformMatrix(long renderNode, long nativeMatrix); + + @CriticalNative + private static native boolean nHasIdentityMatrix(long renderNode); + + // Properties + + @CriticalNative + private static native boolean nOffsetTopAndBottom(long renderNode, int offset); + + @CriticalNative + private static native boolean nOffsetLeftAndRight(long renderNode, int offset); + + @CriticalNative + private static native boolean nSetLeftTopRightBottom(long renderNode, int left, int top, + int right, int bottom); + + @CriticalNative + private static native boolean nSetLeft(long renderNode, int left); + + @CriticalNative + private static native boolean nSetTop(long renderNode, int top); + + @CriticalNative + private static native boolean nSetRight(long renderNode, int right); + + @CriticalNative + private static native boolean nSetBottom(long renderNode, int bottom); + + @CriticalNative + private static native int nGetLeft(long renderNode); + + @CriticalNative + private static native int nGetTop(long renderNode); + + @CriticalNative + private static native int nGetRight(long renderNode); + + @CriticalNative + private static native int nGetBottom(long renderNode); + + @CriticalNative + private static native boolean nSetCameraDistance(long renderNode, float distance); + + @CriticalNative + private static native boolean nSetPivotY(long renderNode, float pivotY); + + @CriticalNative + private static native boolean nSetPivotX(long renderNode, float pivotX); + + @CriticalNative + private static native boolean nResetPivot(long renderNode); + + @CriticalNative + private static native boolean nSetLayerType(long renderNode, int layerType); + + @CriticalNative + private static native int nGetLayerType(long renderNode); + + @CriticalNative + private static native boolean nSetLayerPaint(long renderNode, long paint); + + @CriticalNative + private static native boolean nSetClipToBounds(long renderNode, boolean clipToBounds); + + @CriticalNative + private static native boolean nGetClipToBounds(long renderNode); + + @CriticalNative + private static native boolean nSetClipBounds(long renderNode, int left, int top, + int right, int bottom); + + @CriticalNative + private static native boolean nSetClipBoundsEmpty(long renderNode); + + @CriticalNative + private static native boolean nSetProjectBackwards(long renderNode, boolean shouldProject); + + @CriticalNative + private static native boolean nSetProjectionReceiver(long renderNode, boolean shouldRecieve); + + @CriticalNative + private static native boolean nSetOutlineRoundRect(long renderNode, int left, int top, + int right, int bottom, float radius, float alpha); + + @CriticalNative + private static native boolean nSetOutlineConvexPath(long renderNode, long nativePath, + float alpha); + + @CriticalNative + private static native boolean nSetOutlineEmpty(long renderNode); + + @CriticalNative + private static native boolean nSetOutlineNone(long renderNode); + + @CriticalNative + private static native boolean nHasShadow(long renderNode); + + @CriticalNative + private static native boolean nSetSpotShadowColor(long renderNode, int color); + + @CriticalNative + private static native boolean nSetAmbientShadowColor(long renderNode, int color); + + @CriticalNative + private static native int nGetSpotShadowColor(long renderNode); + + @CriticalNative + private static native int nGetAmbientShadowColor(long renderNode); + + @CriticalNative + private static native boolean nSetClipToOutline(long renderNode, boolean clipToOutline); + + @CriticalNative + private static native boolean nSetRevealClip(long renderNode, + boolean shouldClip, float x, float y, float radius); + + @CriticalNative + private static native boolean nSetAlpha(long renderNode, float alpha); + + @CriticalNative + private static native boolean nSetHasOverlappingRendering(long renderNode, + boolean hasOverlappingRendering); + + @CriticalNative + private static native void nSetUsageHint(long renderNode, int usageHint); + + @CriticalNative + private static native boolean nSetElevation(long renderNode, float lift); + + @CriticalNative + private static native boolean nSetTranslationX(long renderNode, float translationX); + + @CriticalNative + private static native boolean nSetTranslationY(long renderNode, float translationY); + + @CriticalNative + private static native boolean nSetTranslationZ(long renderNode, float translationZ); + + @CriticalNative + private static native boolean nSetRotation(long renderNode, float rotation); + + @CriticalNative + private static native boolean nSetRotationX(long renderNode, float rotationX); + + @CriticalNative + private static native boolean nSetRotationY(long renderNode, float rotationY); + + @CriticalNative + private static native boolean nSetScaleX(long renderNode, float scaleX); + + @CriticalNative + private static native boolean nSetScaleY(long renderNode, float scaleY); + + @CriticalNative + private static native boolean nSetStaticMatrix(long renderNode, long nativeMatrix); + + @CriticalNative + private static native boolean nSetAnimationMatrix(long renderNode, long animationMatrix); + + @CriticalNative + private static native boolean nHasOverlappingRendering(long renderNode); + + @CriticalNative + private static native boolean nGetAnimationMatrix(long renderNode, long animationMatrix); + + @CriticalNative + private static native boolean nGetClipToOutline(long renderNode); + + @CriticalNative + private static native float nGetAlpha(long renderNode); + + @CriticalNative + private static native float nGetCameraDistance(long renderNode); + + @CriticalNative + private static native float nGetScaleX(long renderNode); + + @CriticalNative + private static native float nGetScaleY(long renderNode); + + @CriticalNative + private static native float nGetElevation(long renderNode); + + @CriticalNative + private static native float nGetTranslationX(long renderNode); + + @CriticalNative + private static native float nGetTranslationY(long renderNode); + + @CriticalNative + private static native float nGetTranslationZ(long renderNode); + + @CriticalNative + private static native float nGetRotation(long renderNode); + + @CriticalNative + private static native float nGetRotationX(long renderNode); + + @CriticalNative + private static native float nGetRotationY(long renderNode); + + @CriticalNative + private static native boolean nIsPivotExplicitlySet(long renderNode); + + @CriticalNative + private static native float nGetPivotX(long renderNode); + + @CriticalNative + private static native float nGetPivotY(long renderNode); + + @CriticalNative + private static native int nGetWidth(long renderNode); + + @CriticalNative + private static native int nGetHeight(long renderNode); + + @CriticalNative + private static native boolean nSetAllowForceDark(long renderNode, boolean allowForceDark); + + @CriticalNative + private static native boolean nGetAllowForceDark(long renderNode); + + @CriticalNative + private static native long nGetUniqueId(long renderNode); +} diff --git a/graphics/java/android/graphics/Shader.java b/graphics/java/android/graphics/Shader.java index 40288f5ec8af..3050d1dae5e4 100644 --- a/graphics/java/android/graphics/Shader.java +++ b/graphics/java/android/graphics/Shader.java @@ -16,8 +16,11 @@ package android.graphics; +import android.annotation.ColorInt; +import android.annotation.ColorLong; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UnsupportedAppUsage; import libcore.util.NativeAllocationRegistry; @@ -30,15 +33,41 @@ import libcore.util.NativeAllocationRegistry; public class Shader { private static class NoImagePreloadHolder { - public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( - Shader.class.getClassLoader(), nativeGetFinalizer(), 50); + public static final NativeAllocationRegistry sRegistry = + NativeAllocationRegistry.createMalloced( + Shader.class.getClassLoader(), nativeGetFinalizer()); } /** * @deprecated Use subclass constructors directly instead. */ @Deprecated - public Shader() {} + public Shader() { + mColorSpace = null; + } + + /** + * @hide + */ + public Shader(ColorSpace colorSpace) { + mColorSpace = colorSpace; + if (colorSpace == null) { + throw new IllegalArgumentException( + "Use Shader() to create a Shader with no ColorSpace"); + } + + // This just ensures that if the ColorSpace is invalid, the Exception will be thrown now. + mColorSpace.getNativeInstance(); + } + + private final ColorSpace mColorSpace; + + /** + * @hide + */ + protected ColorSpace colorSpace() { + return mColorSpace; + } /** * Current native shader instance. Created and updated lazily when {@link #getNativeInstance()} @@ -72,6 +101,7 @@ public class Shader { TileMode(int nativeInt) { this.nativeInt = nativeInt; } + @UnsupportedAppUsage final int nativeInt; } @@ -133,21 +163,6 @@ public class Shader { protected void verifyNativeInstance() { } - /** - * @hide - */ - protected Shader copy() { - final Shader copy = new Shader(); - copyLocalMatrix(copy); - return copy; - } - - /** - * @hide - */ - protected void copyLocalMatrix(Shader dest) { - dest.mLocalMatrix.set(mLocalMatrix); - } /** * @hide @@ -167,6 +182,43 @@ public class Shader { return mNativeInstance; } + /** + * @hide + */ + public static @ColorLong long[] convertColors(@NonNull @ColorInt int[] colors) { + if (colors.length < 2) { + throw new IllegalArgumentException("needs >= 2 number of colors"); + } + + long[] colorLongs = new long[colors.length]; + for (int i = 0; i < colors.length; ++i) { + colorLongs[i] = Color.pack(colors[i]); + } + + return colorLongs; + } + + /** + * Detect the ColorSpace that the {@code colors} share. + * + * @throws IllegalArgumentException if the colors do not all share the same, + * valid ColorSpace, or if there are less than 2 colors. + * + * @hide + */ + public static ColorSpace detectColorSpace(@NonNull @ColorLong long[] colors) { + if (colors.length < 2) { + throw new IllegalArgumentException("needs >= 2 number of colors"); + } + final ColorSpace colorSpace = Color.colorSpace(colors[0]); + for (int i = 1; i < colors.length; ++i) { + if (Color.colorSpace(colors[i]) != colorSpace) { + throw new IllegalArgumentException("All colors must be in the same ColorSpace!"); + } + } + return colorSpace; + } + private static native long nativeGetFinalizer(); } diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java index 1eebd2647753..99f440d599cb 100644 --- a/graphics/java/android/graphics/SurfaceTexture.java +++ b/graphics/java/android/graphics/SurfaceTexture.java @@ -17,6 +17,7 @@ package android.graphics; import android.annotation.Nullable; +import android.annotation.UnsupportedAppUsage; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -68,13 +69,17 @@ import java.lang.ref.WeakReference; */ public class SurfaceTexture { private final Looper mCreatorLooper; + @UnsupportedAppUsage private Handler mOnFrameAvailableHandler; /** * These fields are used by native code, do not access or modify. */ + @UnsupportedAppUsage private long mSurfaceTexture; + @UnsupportedAppUsage private long mProducer; + @UnsupportedAppUsage private long mFrameAvailableListener; private boolean mIsSingleBuffered; @@ -378,6 +383,7 @@ public class SurfaceTexture { * This method is invoked from native code only. */ @SuppressWarnings({"UnusedDeclaration"}) + @UnsupportedAppUsage private static void postEventFromNative(WeakReference<SurfaceTexture> weakSelf) { SurfaceTexture st = weakSelf.get(); if (st != null) { @@ -405,6 +411,7 @@ public class SurfaceTexture { private native void nativeSetDefaultBufferSize(int width, int height); private native void nativeUpdateTexImage(); private native void nativeReleaseTexImage(); + @UnsupportedAppUsage private native int nativeDetachFromGLContext(); private native int nativeAttachToGLContext(int texName); private native void nativeRelease(); diff --git a/graphics/java/android/graphics/SweepGradient.java b/graphics/java/android/graphics/SweepGradient.java index b6b80b4f57dc..667f45afe500 100644 --- a/graphics/java/android/graphics/SweepGradient.java +++ b/graphics/java/android/graphics/SweepGradient.java @@ -17,27 +17,52 @@ package android.graphics; import android.annotation.ColorInt; +import android.annotation.ColorLong; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UnsupportedAppUsage; public class SweepGradient extends Shader { - - private static final int TYPE_COLORS_AND_POSITIONS = 1; - private static final int TYPE_COLOR_START_AND_COLOR_END = 2; - - /** - * Type of the LinearGradient: can be either TYPE_COLORS_AND_POSITIONS or - * TYPE_COLOR_START_AND_COLOR_END. - */ - private int mType; - + @UnsupportedAppUsage private float mCx; + @UnsupportedAppUsage private float mCy; - private int[] mColors; + @UnsupportedAppUsage private float[] mPositions; + + // @ColorInts are replaced by @ColorLongs, but these remain due to @UnsupportedAppUsage. + @UnsupportedAppUsage + @ColorInt + private int[] mColors; + @UnsupportedAppUsage + @ColorInt private int mColor0; + @UnsupportedAppUsage + @ColorInt private int mColor1; + @ColorLong + private final long[] mColorLongs; + + /** + * A Shader that draws a sweep gradient around a center point. + * + * @param cx The x-coordinate of the center + * @param cy The y-coordinate of the center + * @param colors The sRGB colors to be distributed between around the center. + * There must be at least 2 colors in the array. + * @param positions May be NULL. The relative position of + * each corresponding color in the colors array, beginning + * with 0 and ending with 1.0. If the values are not + * monotonic, the drawing may produce unexpected results. + * If positions is NULL, then the colors are automatically + * spaced evenly. + */ + public SweepGradient(float cx, float cy, @NonNull @ColorInt int[] colors, + @Nullable float[] positions) { + this(cx, cy, convertColors(colors), positions, ColorSpace.get(ColorSpace.Named.SRGB)); + } + /** * A Shader that draws a sweep gradient around a center point. * @@ -51,20 +76,30 @@ public class SweepGradient extends Shader { * monotonic, the drawing may produce unexpected results. * If positions is NULL, then the colors are automatically * spaced evenly. + * @throws IllegalArgumentException if there are less than two colors, the colors do + * not share the same {@link ColorSpace} or do not use a valid one, or {@code positions} + * is not {@code null} and has a different length from {@code colors}. */ - public SweepGradient(float cx, float cy, - @NonNull @ColorInt int colors[], @Nullable float positions[]) { - if (colors.length < 2) { - throw new IllegalArgumentException("needs >= 2 number of colors"); - } + public SweepGradient(float cx, float cy, @NonNull @ColorLong long[] colors, + @Nullable float[] positions) { + this(cx, cy, colors.clone(), positions, detectColorSpace(colors)); + } + + /** + * Base constructor. Assumes @param colors is a copy that this object can hold onto, + * and all colors share @param colorSpace. + */ + private SweepGradient(float cx, float cy, @NonNull @ColorLong long[] colors, + @Nullable float[] positions, ColorSpace colorSpace) { + super(colorSpace); + if (positions != null && colors.length != positions.length) { throw new IllegalArgumentException( "color and position arrays must be of equal length"); } - mType = TYPE_COLORS_AND_POSITIONS; mCx = cx; mCy = cy; - mColors = colors.clone(); + mColorLongs = colors; mPositions = positions != null ? positions.clone() : null; } @@ -73,47 +108,35 @@ public class SweepGradient extends Shader { * * @param cx The x-coordinate of the center * @param cy The y-coordinate of the center - * @param color0 The color to use at the start of the sweep - * @param color1 The color to use at the end of the sweep + * @param color0 The sRGB color to use at the start of the sweep + * @param color1 The sRGB color to use at the end of the sweep */ public SweepGradient(float cx, float cy, @ColorInt int color0, @ColorInt int color1) { - mType = TYPE_COLOR_START_AND_COLOR_END; - mCx = cx; - mCy = cy; - mColor0 = color0; - mColor1 = color1; - mColors = null; - mPositions = null; - } - - @Override - long createNativeInstance(long nativeMatrix) { - if (mType == TYPE_COLORS_AND_POSITIONS) { - return nativeCreate1(nativeMatrix, mCx, mCy, mColors, mPositions); - } else { // TYPE_COLOR_START_AND_COLOR_END - return nativeCreate2(nativeMatrix, mCx, mCy, mColor0, mColor1); - } + this(cx, cy, Color.pack(color0), Color.pack(color1)); } /** - * @hide + * A Shader that draws a sweep gradient around a center point. + * + * @param cx The x-coordinate of the center + * @param cy The y-coordinate of the center + * @param color0 The color to use at the start of the sweep + * @param color1 The color to use at the end of the sweep + * + * @throws IllegalArgumentException if the colors do + * not share the same {@link ColorSpace} or do not use a valid one. */ + public SweepGradient(float cx, float cy, @ColorLong long color0, @ColorLong long color1) { + this(cx, cy, new long[] {color0, color1}, null); + } + @Override - protected Shader copy() { - final SweepGradient copy; - if (mType == TYPE_COLORS_AND_POSITIONS) { - copy = new SweepGradient(mCx, mCy, mColors.clone(), - mPositions != null ? mPositions.clone() : null); - } else { // TYPE_COLOR_START_AND_COLOR_END - copy = new SweepGradient(mCx, mCy, mColor0, mColor1); - } - copyLocalMatrix(copy); - return copy; + long createNativeInstance(long nativeMatrix) { + return nativeCreate(nativeMatrix, mCx, mCy, mColorLongs, mPositions, + colorSpace().getNativeInstance()); } - private static native long nativeCreate1(long matrix, float x, float y, - int colors[], float positions[]); - private static native long nativeCreate2(long matrix, float x, float y, - int color0, int color1); + private static native long nativeCreate(long matrix, float x, float y, + long[] colors, float[] positions, long colorSpaceHandle); } diff --git a/graphics/java/android/graphics/TableMaskFilter.java b/graphics/java/android/graphics/TableMaskFilter.java index d0c1438285a8..d81c491e07e0 100644 --- a/graphics/java/android/graphics/TableMaskFilter.java +++ b/graphics/java/android/graphics/TableMaskFilter.java @@ -16,6 +16,8 @@ package android.graphics; +import android.annotation.UnsupportedAppUsage; + /** * @hide */ @@ -32,6 +34,7 @@ public class TableMaskFilter extends MaskFilter { native_instance = ni; } + @UnsupportedAppUsage public static TableMaskFilter CreateClipTable(int min, int max) { return new TableMaskFilter(nativeNewClip(min, max)); } diff --git a/graphics/java/android/graphics/TemporaryBuffer.java b/graphics/java/android/graphics/TemporaryBuffer.java index 36a2275738c2..0ae2c703c21c 100644 --- a/graphics/java/android/graphics/TemporaryBuffer.java +++ b/graphics/java/android/graphics/TemporaryBuffer.java @@ -16,12 +16,14 @@ package android.graphics; +import android.annotation.UnsupportedAppUsage; import com.android.internal.util.ArrayUtils; /** * @hide */ public class TemporaryBuffer { + @UnsupportedAppUsage public static char[] obtain(int len) { char[] buf; @@ -37,6 +39,7 @@ public class TemporaryBuffer { return buf; } + @UnsupportedAppUsage public static void recycle(char[] temp) { if (temp.length > 1000) return; diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 18dd97f8acef..6d20ec32cdc4 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -25,15 +25,19 @@ import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UnsupportedAppUsage; import android.content.res.AssetManager; +import android.graphics.fonts.Font; +import android.graphics.fonts.FontFamily; +import android.graphics.fonts.FontStyle; import android.graphics.fonts.FontVariationAxis; -import android.net.Uri; +import android.graphics.fonts.SystemFonts; +import android.os.Build; +import android.os.ParcelFileDescriptor; import android.provider.FontRequest; import android.provider.FontsContract; import android.text.FontConfig; -import android.util.ArrayMap; import android.util.Base64; -import android.util.Log; import android.util.LongSparseArray; import android.util.LruCache; import android.util.SparseArray; @@ -46,18 +50,12 @@ import dalvik.annotation.optimization.CriticalNative; import libcore.util.NativeAllocationRegistry; -import org.xmlpull.v1.XmlPullParserException; - import java.io.File; import java.io.FileDescriptor; -import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -75,8 +73,9 @@ public class Typeface { private static String TAG = "Typeface"; - private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( - Typeface.class.getClassLoader(), nativeGetReleaseFunc(), 64); + private static final NativeAllocationRegistry sRegistry = + NativeAllocationRegistry.createMalloced( + Typeface.class.getClassLoader(), nativeGetReleaseFunc()); /** The default NORMAL typeface object */ public static final Typeface DEFAULT; @@ -93,6 +92,13 @@ public class Typeface { /** The NORMAL style of the default monospace typeface. */ public static final Typeface MONOSPACE; + /** + * The default {@link Typeface}s for different text styles. + * Call {@link #defaultFromStyle(int)} to get the default typeface for the given text style. + * It shouldn't be changed for app wide typeface settings. Please use theme and font XML for + * the same purpose. + */ + @UnsupportedAppUsage(trackingBug = 123769446) static Typeface[] sDefaults; /** @@ -119,12 +125,28 @@ public class Typeface { private static final Object sDynamicCacheLock = new Object(); static Typeface sDefaultTypeface; + + // Following two fields are not used but left for hiddenapi private list + /** + * sSystemFontMap is read only and unmodifiable. + * Use public API {@link #create(String, int)} to get the typeface for given familyName. + */ + @UnsupportedAppUsage(trackingBug = 123769347) static final Map<String, Typeface> sSystemFontMap; - static final Map<String, FontFamily[]> sSystemFallbackMap; + + // We cannot support sSystemFallbackMap since we will migrate to public FontFamily API. + /** + * @deprecated Use {@link android.graphics.fonts.FontFamily} instead. + */ + @UnsupportedAppUsage(trackingBug = 123768928) + @Deprecated + static final Map<String, android.graphics.FontFamily[]> sSystemFallbackMap = + Collections.emptyMap(); /** * @hide */ + @UnsupportedAppUsage public long native_instance; /** @hide */ @@ -139,15 +161,10 @@ public class Typeface { public static final int BOLD_ITALIC = 3; /** @hide */ public static final int STYLE_MASK = 0x03; + @UnsupportedAppUsage private @Style int mStyle = 0; - /** - * A maximum value for the weight value. - * @hide - */ - public static final int MAX_WEIGHT = 1000; - - private @IntRange(from = 0, to = MAX_WEIGHT) int mWeight = 0; + private @IntRange(from = 0, to = FontStyle.FONT_WEIGHT_MAX) int mWeight = 0; // Value for weight and italic. Indicates the value is resolved by font metadata. // Must be the same as the C++ constant in core/jni/android/graphics/FontFamily.cpp @@ -162,6 +179,12 @@ public class Typeface { private int[] mSupportedAxes; private static final int[] EMPTY_AXES = {}; + /** + * Please use font in xml and also your application global theme to change the default Typeface. + * android:textViewStyle and its attribute android:textAppearance can be used in order to change + * typeface and other text related properties. + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) private static void setDefault(Typeface t) { sDefaultTypeface = t; nativeSetDefault(t.native_instance); @@ -189,38 +212,6 @@ public class Typeface { /** * @hide - * Used by Resources to load a font resource of type font file. - */ - @Nullable - public static Typeface createFromResources(AssetManager mgr, String path, int cookie) { - synchronized (sDynamicCacheLock) { - final String key = Builder.createAssetUid( - mgr, path, 0 /* ttcIndex */, null /* axes */, - RESOLVE_BY_FONT_TABLE /* weight */, RESOLVE_BY_FONT_TABLE /* italic */, - DEFAULT_FAMILY); - Typeface typeface = sDynamicTypefaceCache.get(key); - if (typeface != null) return typeface; - - FontFamily fontFamily = new FontFamily(); - // TODO: introduce ttc index and variation settings to resource type font. - if (fontFamily.addFontFromAssetManager(mgr, path, cookie, false /* isAsset */, - 0 /* ttcIndex */, RESOLVE_BY_FONT_TABLE /* weight */, - RESOLVE_BY_FONT_TABLE /* italic */, null /* axes */)) { - if (!fontFamily.freeze()) { - return null; - } - FontFamily[] families = {fontFamily}; - typeface = createFromFamiliesWithDefault(families, DEFAULT_FAMILY, - RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE); - sDynamicTypefaceCache.put(key, typeface); - return typeface; - } - } - return null; - } - - /** - * @hide * Used by Resources to load a font resource of type xml. */ @Nullable @@ -255,21 +246,53 @@ public class Typeface { // family is FontFamilyFilesResourceEntry final FontFamilyFilesResourceEntry filesEntry = (FontFamilyFilesResourceEntry) entry; - FontFamily fontFamily = new FontFamily(); - for (final FontFileResourceEntry fontFile : filesEntry.getEntries()) { - if (!fontFamily.addFontFromAssetManager(mgr, fontFile.getFileName(), - 0 /* resourceCookie */, false /* isAsset */, fontFile.getTtcIndex(), - fontFile.getWeight(), fontFile.getItalic(), - FontVariationAxis.fromFontVariationSettings(fontFile.getVariationSettings()))) { - return null; + try { + FontFamily.Builder familyBuilder = null; + for (final FontFileResourceEntry fontFile : filesEntry.getEntries()) { + final Font.Builder fontBuilder = new Font.Builder(mgr, fontFile.getFileName(), + false /* isAsset */, 0 /* cookie */) + .setTtcIndex(fontFile.getTtcIndex()) + .setFontVariationSettings(fontFile.getVariationSettings()); + if (fontFile.getWeight() != Typeface.RESOLVE_BY_FONT_TABLE) { + fontBuilder.setWeight(fontFile.getWeight()); + } + if (fontFile.getItalic() != Typeface.RESOLVE_BY_FONT_TABLE) { + fontBuilder.setSlant(fontFile.getItalic() == FontFileResourceEntry.ITALIC + ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT); + } + + if (familyBuilder == null) { + familyBuilder = new FontFamily.Builder(fontBuilder.build()); + } else { + familyBuilder.addFont(fontBuilder.build()); + } } - } - if (!fontFamily.freeze()) { + if (familyBuilder == null) { + return Typeface.DEFAULT; + } + final FontFamily family = familyBuilder.build(); + final FontStyle normal = new FontStyle(FontStyle.FONT_WEIGHT_NORMAL, + FontStyle.FONT_SLANT_UPRIGHT); + Font bestFont = family.getFont(0); + int bestScore = normal.getMatchScore(bestFont.getStyle()); + for (int i = 1; i < family.getSize(); ++i) { + final Font candidate = family.getFont(i); + final int score = normal.getMatchScore(candidate.getStyle()); + if (score < bestScore) { + bestFont = candidate; + bestScore = score; + } + } + typeface = new Typeface.CustomFallbackBuilder(family) + .setStyle(bestFont.getStyle()) + .build(); + } catch (IllegalArgumentException e) { + // To be a compatible behavior with API28 or before, catch IllegalArgumentExcetpion + // thrown by native code and returns null. return null; + } catch (IOException e) { + typeface = Typeface.DEFAULT; } - FontFamily[] familyChain = { fontFamily }; - typeface = createFromFamiliesWithDefault(familyChain, DEFAULT_FAMILY, - RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE); synchronized (sDynamicCacheLock) { final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */, null /* axes */, RESOLVE_BY_FONT_TABLE /* weight */, @@ -336,15 +359,11 @@ public class Typeface { /** @hide */ public static final int BOLD_WEIGHT = 700; - private int mTtcIndex; - private FontVariationAxis[] mAxes; - - private AssetManager mAssetManager; - private String mPath; - private FileDescriptor mFd; + // Kept for generating asset cache key. + private final AssetManager mAssetManager; + private final String mPath; - private FontsContract.FontInfo[] mFonts; - private Map<Uri, ByteBuffer> mFontBuffers; + private final @Nullable Font.Builder mFontBuilder; private String mFallbackFamilyName; @@ -357,7 +376,9 @@ public class Typeface { * @param path The file object refers to the font file. */ public Builder(@NonNull File path) { - mPath = path.getAbsolutePath(); + mFontBuilder = new Font.Builder(path); + mAssetManager = null; + mPath = null; } /** @@ -369,7 +390,18 @@ public class Typeface { * @param fd The file descriptor. The passed fd must be mmap-able. */ public Builder(@NonNull FileDescriptor fd) { - mFd = fd; + Font.Builder builder; + try { + builder = new Font.Builder(ParcelFileDescriptor.dup(fd)); + } catch (IOException e) { + // We cannot tell the error to developer at this moment since we cannot change the + // public API signature. Instead, silently fallbacks to system fallback in the build + // method as the same as other error cases. + builder = null; + } + mFontBuilder = builder; + mAssetManager = null; + mPath = null; } /** @@ -378,7 +410,9 @@ public class Typeface { * @param path The full path to the font file. */ public Builder(@NonNull String path) { - mPath = path; + mFontBuilder = new Font.Builder(new File(path)); + mAssetManager = null; + mPath = null; } /** @@ -388,27 +422,22 @@ public class Typeface { * @param path The file name of the font data in the asset directory */ public Builder(@NonNull AssetManager assetManager, @NonNull String path) { - mAssetManager = Preconditions.checkNotNull(assetManager); - mPath = Preconditions.checkStringNotEmpty(path); + this(assetManager, path, true /* is asset */, 0 /* cookie */); } /** - * Constracts a builder from an array of FontsContract.FontInfo. - * - * Since {@link FontsContract.FontInfo} holds information about TTC indices and - * variation settings, there is no need to call {@link #setTtcIndex} or - * {@link #setFontVariationSettings}. Similary, {@link FontsContract.FontInfo} holds - * weight and italic information, so {@link #setWeight} and {@link #setItalic} are used - * for style matching during font selection. + * Constructs a builder from an asset manager and a file path in an asset directory. * - * @param fonts The array of {@link FontsContract.FontInfo} - * @param buffers The mapping from URI to buffers to be used during building. + * @param assetManager The application's asset manager + * @param path The file name of the font data in the asset directory + * @param cookie a cookie for the asset * @hide */ - public Builder(@NonNull FontsContract.FontInfo[] fonts, - @NonNull Map<Uri, ByteBuffer> buffers) { - mFonts = fonts; - mFontBuffers = buffers; + public Builder(@NonNull AssetManager assetManager, @NonNull String path, boolean isAsset, + int cookie) { + mFontBuilder = new Font.Builder(assetManager, path, isAsset, cookie); + mAssetManager = assetManager; + mPath = path; } /** @@ -420,6 +449,7 @@ public class Typeface { */ public Builder setWeight(@IntRange(from = 1, to = 1000) int weight) { mWeight = weight; + mFontBuilder.setWeight(weight); return this; } @@ -431,7 +461,8 @@ public class Typeface { * @param italic {@code true} if the font is italic. Otherwise {@code false}. */ public Builder setItalic(boolean italic) { - mItalic = italic ? STYLE_ITALIC : STYLE_NORMAL; + mItalic = italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT; + mFontBuilder.setSlant(mItalic); return this; } @@ -443,11 +474,7 @@ public class Typeface { * collection, do not call this method or specify 0. */ public Builder setTtcIndex(@IntRange(from = 0) int ttcIndex) { - if (mFonts != null) { - throw new IllegalArgumentException( - "TTC index can not be specified for FontResult source."); - } - mTtcIndex = ttcIndex; + mFontBuilder.setTtcIndex(ttcIndex); return this; } @@ -459,14 +486,7 @@ public class Typeface { * format. */ public Builder setFontVariationSettings(@Nullable String variationSettings) { - if (mFonts != null) { - throw new IllegalArgumentException( - "Font variation settings can not be specified for FontResult source."); - } - if (mAxes != null) { - throw new IllegalStateException("Font variation settings are already set."); - } - mAxes = FontVariationAxis.fromFontVariationSettings(variationSettings); + mFontBuilder.setFontVariationSettings(variationSettings); return this; } @@ -476,14 +496,7 @@ public class Typeface { * @param axes An array of font variation axis tag-value pairs. */ public Builder setFontVariationSettings(@Nullable FontVariationAxis[] axes) { - if (mFonts != null) { - throw new IllegalArgumentException( - "Font variation settings can not be specified for FontResult source."); - } - if (mAxes != null) { - throw new IllegalStateException("Font variation settings are already set."); - } - mAxes = axes; + mFontBuilder.setFontVariationSettings(axes); return this; } @@ -559,11 +572,7 @@ public class Typeface { return null; } - Typeface base = sSystemFontMap.get(mFallbackFamilyName); - if (base == null) { - base = sDefaultTypeface; - } - + final Typeface base = getSystemDefaultTypeface(mFallbackFamilyName); if (mWeight == RESOLVE_BY_FONT_TABLE && mItalic == RESOLVE_BY_FONT_TABLE) { return base; } @@ -580,91 +589,209 @@ public class Typeface { * @return Newly created Typeface. May return null if some parameters are invalid. */ public Typeface build() { - if (mFd != null) { // Builder is created with file descriptor. - try (FileInputStream fis = new FileInputStream(mFd)) { - FileChannel channel = fis.getChannel(); - long size = channel.size(); - ByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, size); - - final FontFamily fontFamily = new FontFamily(); - if (!fontFamily.addFontFromBuffer(buffer, mTtcIndex, mAxes, mWeight, mItalic)) { - fontFamily.abortCreation(); - return resolveFallbackTypeface(); - } - if (!fontFamily.freeze()) { - return resolveFallbackTypeface(); - } - FontFamily[] families = { fontFamily }; - return createFromFamiliesWithDefault(families, mFallbackFamilyName, mWeight, - mItalic); - } catch (IOException e) { - return resolveFallbackTypeface(); - } - } else if (mAssetManager != null) { // Builder is created with asset manager. - final String key = createAssetUid( - mAssetManager, mPath, mTtcIndex, mAxes, mWeight, mItalic, - mFallbackFamilyName); - synchronized (sDynamicCacheLock) { - Typeface typeface = sDynamicTypefaceCache.get(key); - if (typeface != null) return typeface; - final FontFamily fontFamily = new FontFamily(); - if (!fontFamily.addFontFromAssetManager(mAssetManager, mPath, mTtcIndex, - true /* isAsset */, mTtcIndex, mWeight, mItalic, mAxes)) { - fontFamily.abortCreation(); - return resolveFallbackTypeface(); - } - if (!fontFamily.freeze()) { - return resolveFallbackTypeface(); + if (mFontBuilder == null) { + return resolveFallbackTypeface(); + } + try { + final Font font = mFontBuilder.build(); + final String key = mAssetManager == null ? null : createAssetUid( + mAssetManager, mPath, font.getTtcIndex(), font.getAxes(), + mWeight, mItalic, + mFallbackFamilyName == null ? DEFAULT_FAMILY : mFallbackFamilyName); + if (key != null) { + // Dynamic cache lookup is only for assets. + synchronized (sDynamicCacheLock) { + final Typeface typeface = sDynamicTypefaceCache.get(key); + if (typeface != null) { + return typeface; + } } - FontFamily[] families = { fontFamily }; - typeface = createFromFamiliesWithDefault(families, mFallbackFamilyName, - mWeight, mItalic); - sDynamicTypefaceCache.put(key, typeface); - return typeface; - } - } else if (mPath != null) { // Builder is created with file path. - final FontFamily fontFamily = new FontFamily(); - if (!fontFamily.addFont(mPath, mTtcIndex, mAxes, mWeight, mItalic)) { - fontFamily.abortCreation(); - return resolveFallbackTypeface(); } - if (!fontFamily.freeze()) { - return resolveFallbackTypeface(); + final FontFamily family = new FontFamily.Builder(font).build(); + final int weight = mWeight == RESOLVE_BY_FONT_TABLE + ? font.getStyle().getWeight() : mWeight; + final int slant = mItalic == RESOLVE_BY_FONT_TABLE + ? font.getStyle().getSlant() : mItalic; + final CustomFallbackBuilder builder = new CustomFallbackBuilder(family) + .setStyle(new FontStyle(weight, slant)); + if (mFallbackFamilyName != null) { + builder.setSystemFallback(mFallbackFamilyName); } - FontFamily[] families = { fontFamily }; - return createFromFamiliesWithDefault(families, mFallbackFamilyName, mWeight, - mItalic); - } else if (mFonts != null) { - final FontFamily fontFamily = new FontFamily(); - boolean atLeastOneFont = false; - for (FontsContract.FontInfo font : mFonts) { - final ByteBuffer fontBuffer = mFontBuffers.get(font.getUri()); - if (fontBuffer == null) { - continue; // skip + final Typeface typeface = builder.build(); + if (key != null) { + synchronized (sDynamicCacheLock) { + sDynamicTypefaceCache.put(key, typeface); } - final boolean success = fontFamily.addFontFromBuffer(fontBuffer, - font.getTtcIndex(), font.getAxes(), font.getWeight(), - font.isItalic() ? STYLE_ITALIC : STYLE_NORMAL); - if (!success) { - fontFamily.abortCreation(); - return null; - } - atLeastOneFont = true; - } - if (!atLeastOneFont) { - // No fonts are avaialble. No need to create new Typeface and returns fallback - // Typeface instead. - fontFamily.abortCreation(); - return null; } - fontFamily.freeze(); - FontFamily[] families = { fontFamily }; - return createFromFamiliesWithDefault(families, mFallbackFamilyName, mWeight, - mItalic); + return typeface; + } catch (IOException | IllegalArgumentException e) { + return resolveFallbackTypeface(); } + } + } + + /** + * A builder class for creating new Typeface instance. + * + * There are two font fallback mechanisms, custom font fallback and system font fallback. + * The custom font fallback is a simple ordered list. The text renderer tries to see if it can + * render a character with the first font and if that font does not support the character, try + * next one and so on. It will keep trying until end of the custom fallback chain. The maximum + * length of the custom fallback chain is 64. + * The system font fallback is a system pre-defined fallback chain. The system fallback is + * processed only when no matching font is found in the custom font fallback. + * + * <p> + * Examples, + * 1) Create Typeface from single ttf file. + * <pre> + * <code> + * Font font = new Font.Builder("your_font_file.ttf").build(); + * FontFamily family = new FontFamily.Builder(font).build(); + * Typeface typeface = new Typeface.CustomFallbackBuilder(family).build(); + * </code> + * </pre> + * + * 2) Create Typeface from multiple font files and select bold style by default. + * <pre> + * <code> + * Font regularFont = new Font.Builder("regular.ttf").build(); + * Font boldFont = new Font.Builder("bold.ttf").build(); + * FontFamily family = new FontFamily.Builder(regularFont) + * .addFont(boldFont).build(); + * Typeface typeface = new Typeface.CustomFallbackBuilder(family) + * .setWeight(Font.FONT_WEIGHT_BOLD) // Set bold style as the default style. + * // If the font family doesn't have bold style font, + * // system will select the closest font. + * .build(); + * </code> + * </pre> + * + * 3) Create Typeface from single ttf file and if that font does not have glyph for the + * characters, use "serif" font family instead. + * <pre> + * <code> + * Font font = new Font.Builder("your_font_file.ttf").build(); + * FontFamily family = new FontFamily.Builder(font).build(); + * Typeface typeface = new Typeface.CustomFallbackBuilder(family) + * .setSystemFallback("serif") // Set serif font family as the fallback. + * .build(); + * </code> + * </pre> + * 4) Create Typeface from single ttf file and set another ttf file for the fallback. + * <pre> + * <code> + * Font font = new Font.Builder("English.ttf").build(); + * FontFamily family = new FontFamily.Builder(font).build(); + * + * Font fallbackFont = new Font.Builder("Arabic.ttf").build(); + * FontFamily fallbackFamily = new FontFamily.Builder(fallbackFont).build(); + * Typeface typeface = new Typeface.CustomFallbackBuilder(family) + * .addCustomFallback(fallbackFamily) // Specify fallback family. + * .setSystemFallback("serif") // Set serif font family as the fallback. + * .build(); + * </code> + * </pre> + * </p> + */ + public static final class CustomFallbackBuilder { + private static final int MAX_CUSTOM_FALLBACK = 64; + private final ArrayList<FontFamily> mFamilies = new ArrayList<>(); + private String mFallbackName = null; + private @Nullable FontStyle mStyle; - // Must not reach here. - throw new IllegalArgumentException("No source was set."); + /** + * Returns the maximum capacity of custom fallback families. + * + * This includes the the first font family passed to the constructor. + * It is guaranteed that the value will be greater than or equal to 64. + * + * @return the maximum number of font families for the custom fallback + */ + public static @IntRange(from = 64) int getMaxCustomFallbackCount() { + return MAX_CUSTOM_FALLBACK; + } + + /** + * Constructs a builder with a font family. + * + * @param family a family object + */ + public CustomFallbackBuilder(@NonNull FontFamily family) { + Preconditions.checkNotNull(family); + mFamilies.add(family); + } + + /** + * Sets a system fallback by name. + * + * You can specify generic font familiy names or OEM specific family names. If the system + * don't have a specified fallback, the default fallback is used instead. + * For more information about generic font families, see <a + * href="https://www.w3.org/TR/css-fonts-4/#generic-font-families">CSS specification</a> + * + * For more information about fallback, see class description. + * + * @param familyName a family name to be used for fallback if the provided fonts can not be + * used + */ + public @NonNull CustomFallbackBuilder setSystemFallback(@NonNull String familyName) { + Preconditions.checkNotNull(familyName); + mFallbackName = familyName; + return this; + } + + /** + * Sets a font style of the Typeface. + * + * If the font family doesn't have a font of given style, system will select the closest + * font from font family. For example, if a font family has fonts of 300 weight and 700 + * weight then setWeight(400) is called, system will select the font of 300 weight. + * + * @param style a font style + */ + public @NonNull CustomFallbackBuilder setStyle(@NonNull FontStyle style) { + mStyle = style; + return this; + } + + /** + * Append a font family to the end of the custom font fallback. + * + * You can set up to 64 custom fallback families including the first font family you passed + * to the constructor. + * For more information about fallback, see class description. + * + * @param family a fallback family + * @throws IllegalArgumentException if you give more than 64 custom fallback families + */ + public @NonNull CustomFallbackBuilder addCustomFallback(@NonNull FontFamily family) { + Preconditions.checkNotNull(family); + Preconditions.checkArgument(mFamilies.size() < getMaxCustomFallbackCount(), + "Custom fallback limit exceeded(" + getMaxCustomFallbackCount() + ")"); + mFamilies.add(family); + return this; + } + + /** + * Create the Typeface based on the configured values. + * + * @return the Typeface object + */ + public @NonNull Typeface build() { + final int userFallbackSize = mFamilies.size(); + final FontFamily[] fallback = SystemFonts.getSystemFallback(mFallbackName); + final long[] ptrArray = new long[fallback.length + userFallbackSize]; + for (int i = 0; i < userFallbackSize; ++i) { + ptrArray[i] = mFamilies.get(i).getNativePtr(); + } + for (int i = 0; i < fallback.length; ++i) { + ptrArray[i + userFallbackSize] = fallback[i].getNativePtr(); + } + final int weight = mStyle == null ? 400 : mStyle.getWeight(); + final int italic = + (mStyle == null || mStyle.getSlant() == FontStyle.FONT_SLANT_UPRIGHT) ? 0 : 1; + return new Typeface(nativeCreateFromArray(ptrArray, weight, italic)); } } @@ -680,7 +807,7 @@ public class Typeface { * @return The best matching typeface. */ public static Typeface create(String familyName, @Style int style) { - return create(sSystemFontMap.get(familyName), style); + return create(getSystemDefaultTypeface(familyName), style); } /** @@ -797,8 +924,7 @@ public class Typeface { } typeface = new Typeface( - nativeCreateFromTypefaceWithExactStyle( - base.native_instance, weight, italic)); + nativeCreateFromTypefaceWithExactStyle(base.native_instance, weight, italic)); innerCache.put(key, typeface); } return typeface; @@ -807,8 +933,8 @@ public class Typeface { /** @hide */ public static Typeface createFromTypefaceWithVariation(@Nullable Typeface family, @NonNull List<FontVariationAxis> axes) { - final long ni = family == null ? 0 : family.native_instance; - return new Typeface(nativeCreateFromTypefaceWithVariation(ni, axes)); + final Typeface base = family == null ? Typeface.DEFAULT : family; + return new Typeface(nativeCreateFromTypefaceWithVariation(base.native_instance, axes)); } /** @@ -890,8 +1016,11 @@ public class Typeface { * Create a new typeface from an array of font families. * * @param families array of font families + * @deprecated */ - private static Typeface createFromFamilies(FontFamily[] families) { + @Deprecated + @UnsupportedAppUsage(trackingBug = 123768928) + private static Typeface createFromFamilies(android.graphics.FontFamily[] families) { long[] ptrArray = new long[families.length]; for (int i = 0; i < families.length; i++) { ptrArray[i] = families[i].mNativePtr; @@ -901,11 +1030,28 @@ public class Typeface { } /** + * Create a new typeface from an array of android.graphics.fonts.FontFamily. + * + * @param families array of font families + */ + private static Typeface createFromFamilies(@Nullable FontFamily[] families) { + final long[] ptrArray = new long[families.length]; + for (int i = 0; i < families.length; ++i) { + ptrArray[i] = families[i].getNativePtr(); + } + return new Typeface(nativeCreateFromArray(ptrArray, + RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)); + } + + /** * This method is used by supportlib-v27. - * TODO: Remove private API use in supportlib: http://b/72665240 + * + * @deprecated Use {@link android.graphics.fonts.FontFamily} instead. */ - private static Typeface createFromFamiliesWithDefault(FontFamily[] families, int weight, - int italic) { + @UnsupportedAppUsage(trackingBug = 123768395) + @Deprecated + private static Typeface createFromFamiliesWithDefault( + android.graphics.FontFamily[] families, int weight, int italic) { return createFromFamiliesWithDefault(families, DEFAULT_FAMILY, weight, italic); } @@ -921,24 +1067,26 @@ public class Typeface { * the first family's font is used. If the first family has multiple fonts, the * closest to the regular weight and upright font is used. * @param families array of font families + * + * @deprecated Use {@link android.graphics.fonts.FontFamily} instead. */ - private static Typeface createFromFamiliesWithDefault(FontFamily[] families, + @UnsupportedAppUsage(trackingBug = 123768928) + @Deprecated + private static Typeface createFromFamiliesWithDefault(android.graphics.FontFamily[] families, String fallbackName, int weight, int italic) { - FontFamily[] fallback = sSystemFallbackMap.get(fallbackName); - if (fallback == null) { - fallback = sSystemFallbackMap.get(DEFAULT_FAMILY); - } + android.graphics.fonts.FontFamily[] fallback = SystemFonts.getSystemFallback(fallbackName); long[] ptrArray = new long[families.length + fallback.length]; for (int i = 0; i < families.length; i++) { ptrArray[i] = families[i].mNativePtr; } for (int i = 0; i < fallback.length; i++) { - ptrArray[i + families.length] = fallback[i].mNativePtr; + ptrArray[i + families.length] = fallback[i].getNativePtr(); } return new Typeface(nativeCreateFromArray(ptrArray, weight, italic)); } // don't allow clients to call this directly + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private Typeface(long ni) { if (ni == 0) { throw new RuntimeException("native typeface cannot be made"); @@ -950,193 +1098,54 @@ public class Typeface { mWeight = nativeGetWeight(ni); } - private static @Nullable ByteBuffer mmap(String fullPath) { - try (FileInputStream file = new FileInputStream(fullPath)) { - final FileChannel fileChannel = file.getChannel(); - final long fontSize = fileChannel.size(); - return fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize); - } catch (IOException e) { - Log.e(TAG, "Error mapping font file " + fullPath); - return null; - } + private static Typeface getSystemDefaultTypeface(@NonNull String familyName) { + Typeface tf = sSystemFontMap.get(familyName); + return tf == null ? Typeface.DEFAULT : tf; } - private static @Nullable FontFamily createFontFamily( - String familyName, List<FontConfig.Font> fonts, String[] languageTags, int variant, - Map<String, ByteBuffer> cache, String fontDir) { - final FontFamily family = new FontFamily(languageTags, variant); - for (int i = 0; i < fonts.size(); i++) { - final FontConfig.Font font = fonts.get(i); - final String fullPath = fontDir + font.getFontName(); - ByteBuffer buffer = cache.get(fullPath); - if (buffer == null) { - if (cache.containsKey(fullPath)) { - continue; // Already failed to mmap. Skip it. - } - buffer = mmap(fullPath); - cache.put(fullPath, buffer); - if (buffer == null) { - continue; - } - } - if (!family.addFontFromBuffer(buffer, font.getTtcIndex(), font.getAxes(), - font.getWeight(), font.isItalic() ? STYLE_ITALIC : STYLE_NORMAL)) { - Log.e(TAG, "Error creating font " + fullPath + "#" + font.getTtcIndex()); - } - } - if (!family.freeze()) { - Log.e(TAG, "Unable to load Family: " + familyName + " : " - + Arrays.toString(languageTags)); - return null; + /** @hide */ + @VisibleForTesting + public static void initSystemDefaultTypefaces(Map<String, Typeface> systemFontMap, + Map<String, FontFamily[]> fallbacks, + FontConfig.Alias[] aliases) { + for (Map.Entry<String, FontFamily[]> entry : fallbacks.entrySet()) { + systemFontMap.put(entry.getKey(), createFromFamilies(entry.getValue())); } - return family; - } - - private static void pushFamilyToFallback(FontConfig.Family xmlFamily, - ArrayMap<String, ArrayList<FontFamily>> fallbackMap, - Map<String, ByteBuffer> cache, - String fontDir) { - - final String[] languageTags = xmlFamily.getLanguages(); - final int variant = xmlFamily.getVariant(); - final ArrayList<FontConfig.Font> defaultFonts = new ArrayList<>(); - final ArrayMap<String, ArrayList<FontConfig.Font>> specificFallbackFonts = new ArrayMap<>(); - - // Collect default fallback and specific fallback fonts. - for (final FontConfig.Font font : xmlFamily.getFonts()) { - final String fallbackName = font.getFallbackFor(); - if (fallbackName == null) { - defaultFonts.add(font); - } else { - ArrayList<FontConfig.Font> fallback = specificFallbackFonts.get(fallbackName); - if (fallback == null) { - fallback = new ArrayList<>(); - specificFallbackFonts.put(fallbackName, fallback); - } - fallback.add(font); + for (FontConfig.Alias alias : aliases) { + if (systemFontMap.containsKey(alias.getName())) { + continue; // If alias and named family are conflict, use named family. } - } - - final FontFamily defaultFamily = defaultFonts.isEmpty() ? null : createFontFamily( - xmlFamily.getName(), defaultFonts, languageTags, variant, cache, fontDir); - - // Insert family into fallback map. - for (int i = 0; i < fallbackMap.size(); i++) { - final ArrayList<FontConfig.Font> fallback = - specificFallbackFonts.get(fallbackMap.keyAt(i)); - if (fallback == null) { - if (defaultFamily != null) { - fallbackMap.valueAt(i).add(defaultFamily); - } - } else { - final FontFamily family = createFontFamily( - xmlFamily.getName(), fallback, languageTags, variant, cache, fontDir); - if (family != null) { - fallbackMap.valueAt(i).add(family); - } else if (defaultFamily != null) { - fallbackMap.valueAt(i).add(defaultFamily); - } else { - // There is no valid for for default fallback. Ignore. - } + final Typeface base = systemFontMap.get(alias.getToName()); + if (base == null) { + // The missing target is a valid thing, some configuration don't have font files, + // e.g. wear devices. Just skip this alias. + continue; } + final int weight = alias.getWeight(); + final Typeface newFace = weight == 400 ? base : + new Typeface(nativeCreateWeightAlias(base.native_instance, weight)); + systemFontMap.put(alias.getName(), newFace); } } - /** - * Build the system fallback from xml file. - * - * @param xmlPath A full path string to the fonts.xml file. - * @param fontDir A full path string to the system font directory. This must end with - * slash('/'). - * @param fontMap An output system font map. Caller must pass empty map. - * @param fallbackMap An output system fallback map. Caller must pass empty map. - * @hide - */ - @VisibleForTesting - public static void buildSystemFallback(String xmlPath, String fontDir, - ArrayMap<String, Typeface> fontMap, ArrayMap<String, FontFamily[]> fallbackMap) { - try { - final FileInputStream fontsIn = new FileInputStream(xmlPath); - final FontConfig fontConfig = FontListParser.parse(fontsIn); - - final HashMap<String, ByteBuffer> bufferCache = new HashMap<String, ByteBuffer>(); - final FontConfig.Family[] xmlFamilies = fontConfig.getFamilies(); - - final ArrayMap<String, ArrayList<FontFamily>> fallbackListMap = new ArrayMap<>(); - // First traverse families which have a 'name' attribute to create fallback map. - for (final FontConfig.Family xmlFamily : xmlFamilies) { - final String familyName = xmlFamily.getName(); - if (familyName == null) { - continue; - } - final FontFamily family = createFontFamily( - xmlFamily.getName(), Arrays.asList(xmlFamily.getFonts()), - xmlFamily.getLanguages(), xmlFamily.getVariant(), bufferCache, fontDir); - if (family == null) { - continue; - } - final ArrayList<FontFamily> fallback = new ArrayList<>(); - fallback.add(family); - fallbackListMap.put(familyName, fallback); - } - - // Then, add fallback fonts to the each fallback map. - for (int i = 0; i < xmlFamilies.length; i++) { - final FontConfig.Family xmlFamily = xmlFamilies[i]; - // The first family (usually the sans-serif family) is always placed immediately - // after the primary family in the fallback. - if (i == 0 || xmlFamily.getName() == null) { - pushFamilyToFallback(xmlFamily, fallbackListMap, bufferCache, fontDir); - } - } - - // Build the font map and fallback map. - for (int i = 0; i < fallbackListMap.size(); i++) { - final String fallbackName = fallbackListMap.keyAt(i); - final List<FontFamily> familyList = fallbackListMap.valueAt(i); - final FontFamily[] families = familyList.toArray(new FontFamily[familyList.size()]); - - fallbackMap.put(fallbackName, families); - final long[] ptrArray = new long[families.length]; - for (int j = 0; j < families.length; j++) { - ptrArray[j] = families[j].mNativePtr; - } - fontMap.put(fallbackName, new Typeface(nativeCreateFromArray( - ptrArray, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE))); - } - - // Insert alias to font maps. - for (final FontConfig.Alias alias : fontConfig.getAliases()) { - Typeface base = fontMap.get(alias.getToName()); - Typeface newFace = base; - int weight = alias.getWeight(); - if (weight != 400) { - newFace = new Typeface(nativeCreateWeightAlias(base.native_instance, weight)); - } - fontMap.put(alias.getName(), newFace); - } - } catch (RuntimeException e) { - Log.w(TAG, "Didn't create default family (most likely, non-Minikin build)", e); - // TODO: normal in non-Minikin case, remove or make error when Minikin-only - } catch (FileNotFoundException e) { - Log.e(TAG, "Error opening " + xmlPath, e); - } catch (IOException e) { - Log.e(TAG, "Error reading " + xmlPath, e); - } catch (XmlPullParserException e) { - Log.e(TAG, "XML parse exception for " + xmlPath, e); + private static void registerGenericFamilyNative(@NonNull String familyName, + @Nullable Typeface typeface) { + if (typeface != null) { + nativeRegisterGenericFamily(familyName, typeface.native_instance); } } static { - final ArrayMap<String, Typeface> systemFontMap = new ArrayMap<>(); - final ArrayMap<String, FontFamily[]> systemFallbackMap = new ArrayMap<>(); - buildSystemFallback("/system/etc/fonts.xml", "/system/fonts/", systemFontMap, - systemFallbackMap); + final HashMap<String, Typeface> systemFontMap = new HashMap<>(); + initSystemDefaultTypefaces(systemFontMap, SystemFonts.getRawSystemFallbackMap(), + SystemFonts.getAliases()); sSystemFontMap = Collections.unmodifiableMap(systemFontMap); - sSystemFallbackMap = Collections.unmodifiableMap(systemFallbackMap); - setDefault(sSystemFontMap.get(DEFAULT_FAMILY)); + // We can't assume DEFAULT_FAMILY available on Roboletric. + if (sSystemFontMap.containsKey(DEFAULT_FAMILY)) { + setDefault(sSystemFontMap.get(DEFAULT_FAMILY)); + } // Set up defaults and typefaces exposed in public API DEFAULT = create((String) null, 0); @@ -1152,6 +1161,15 @@ public class Typeface { create((String) null, Typeface.BOLD_ITALIC), }; + // A list of generic families to be registered in native. + // https://www.w3.org/TR/css-fonts-4/#generic-font-families + String[] genericFamilies = { + "serif", "sans-serif", "cursive", "fantasy", "monospace", "system-ui" + }; + + for (String genericFamily : genericFamilies) { + registerGenericFamilyNative(genericFamily, systemFontMap.get(genericFamily)); + } } @Override @@ -1197,7 +1215,9 @@ public class Typeface { // TODO: clean up: change List<FontVariationAxis> to FontVariationAxis[] private static native long nativeCreateFromTypefaceWithVariation( long native_instance, List<FontVariationAxis> axes); + @UnsupportedAppUsage private static native long nativeCreateWeightAlias(long native_instance, int weight); + @UnsupportedAppUsage private static native long nativeCreateFromArray(long[] familyArray, int weight, int italic); private static native int[] nativeGetSupportedAxes(long native_instance); @@ -1212,4 +1232,6 @@ public class Typeface { @CriticalNative private static native long nativeGetReleaseFunc(); + + private static native void nativeRegisterGenericFamily(String str, long nativePtr); } diff --git a/graphics/java/android/graphics/Xfermode.java b/graphics/java/android/graphics/Xfermode.java index a5da5d09ebaf..6f4adfde7ff9 100644 --- a/graphics/java/android/graphics/Xfermode.java +++ b/graphics/java/android/graphics/Xfermode.java @@ -21,6 +21,8 @@ package android.graphics; +import android.annotation.UnsupportedAppUsage; + /** * Xfermode is the base class for objects that are called to implement custom * "transfer-modes" in the drawing pipeline. The static function Create(Modes) @@ -30,5 +32,6 @@ package android.graphics; */ public class Xfermode { static final int DEFAULT = PorterDuff.Mode.SRC_OVER.nativeInt; + @UnsupportedAppUsage int porterDuffMode = DEFAULT; } diff --git a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java index fdd638adba81..fab96a1e9fbd 100644 --- a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java +++ b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java @@ -19,6 +19,7 @@ package android.graphics.drawable; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; +import android.app.ActivityThread; import android.content.pm.ActivityInfo.Config; import android.content.res.ColorStateList; import android.content.res.Resources; @@ -26,6 +27,7 @@ import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.BitmapShader; +import android.graphics.BlendMode; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; @@ -34,7 +36,6 @@ import android.graphics.Outline; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PixelFormat; -import android.graphics.PorterDuff.Mode; import android.graphics.Rect; import android.graphics.Region; import android.graphics.Shader; @@ -108,11 +109,10 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback * Scaled mask based on the view bounds. */ private final Path mMask; + private final Path mMaskScaleOnly; private final Matrix mMaskMatrix; private final Region mTransparentRegion; - private Bitmap mMaskBitmap; - /** * Indices used to access {@link #mLayerState.mChildDrawable} array for foreground and * background layer. @@ -151,13 +151,16 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback */ AdaptiveIconDrawable(@Nullable LayerState state, @Nullable Resources res) { mLayerState = createConstantState(state, res); - - if (sMask == null) { - sMask = PathParser.createPathFromPathData( - Resources.getSystem().getString(R.string.config_icon_mask)); - } - mMask = PathParser.createPathFromPathData( - Resources.getSystem().getString(R.string.config_icon_mask)); + // config_icon_mask from context bound resource may have been chaged using + // OverlayManager. Read that one first. + Resources r = ActivityThread.currentActivityThread() == null + ? Resources.getSystem() + : ActivityThread.currentActivityThread().getApplication().getResources(); + // TODO: either make sMask update only when config_icon_mask changes OR + // get rid of it all-together in layoutlib + sMask = PathParser.createPathFromPathData(r.getString(R.string.config_icon_mask)); + mMask = new Path(sMask); + mMaskScaleOnly = new Path(mMask); mMaskMatrix = new Matrix(); mCanvas = new Canvas(); mTransparentRegion = new Region(); @@ -329,24 +332,19 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback } private void updateMaskBoundsInternal(Rect b) { + // reset everything that depends on the view bounds mMaskMatrix.setScale(b.width() / MASK_SIZE, b.height() / MASK_SIZE); + sMask.transform(mMaskMatrix, mMaskScaleOnly); + + mMaskMatrix.postTranslate(b.left, b.top); sMask.transform(mMaskMatrix, mMask); - if (mMaskBitmap == null || mMaskBitmap.getWidth() != b.width() || - mMaskBitmap.getHeight() != b.height()) { - mMaskBitmap = Bitmap.createBitmap(b.width(), b.height(), Bitmap.Config.ALPHA_8); + if (mLayersBitmap == null || mLayersBitmap.getWidth() != b.width() + || mLayersBitmap.getHeight() != b.height()) { mLayersBitmap = Bitmap.createBitmap(b.width(), b.height(), Bitmap.Config.ARGB_8888); } - // mMaskBitmap bound [0, w] x [0, h] - mCanvas.setBitmap(mMaskBitmap); - mPaint.setShader(null); - mCanvas.drawPath(mMask, mPaint); - // mMask bound [left, top, right, bottom] - mMaskMatrix.postTranslate(b.left, b.top); - mMask.reset(); - sMask.transform(mMaskMatrix, mMask); - // reset everything that depends on the view bounds + mPaint.setShader(null); mTransparentRegion.setEmpty(); mLayersShader = null; } @@ -371,9 +369,11 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback mLayersShader = new BitmapShader(mLayersBitmap, TileMode.CLAMP, TileMode.CLAMP); mPaint.setShader(mLayersShader); } - if (mMaskBitmap != null) { + if (mMaskScaleOnly != null) { Rect bounds = getBounds(); - canvas.drawBitmap(mMaskBitmap, bounds.left, bounds.top, mPaint); + canvas.translate(bounds.left, bounds.top); + canvas.drawPath(mMaskScaleOnly, mPaint); + canvas.translate(-bounds.left, -bounds.top); } } @@ -549,7 +549,7 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback final ChildDrawable[] layers = mLayerState.mChildren; for (int i = 0; i < mLayerState.N_CHILDREN; i++) { - if (layers[i].mDrawable.isProjected()) { + if (layers[i].mDrawable != null && layers[i].mDrawable.isProjected()) { return true; } } @@ -674,7 +674,7 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback @Override public int getAlpha() { - return PixelFormat.TRANSLUCENT; + return mPaint.getAlpha(); } @Override @@ -701,13 +701,13 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback } @Override - public void setTintMode(Mode tintMode) { + public void setTintBlendMode(@NonNull BlendMode blendMode) { final ChildDrawable[] array = mLayerState.mChildren; final int N = mLayerState.N_CHILDREN; for (int i = 0; i < N; i++) { final Drawable dr = array[i].mDrawable; if (dr != null) { - dr.setTintMode(tintMode); + dr.setTintBlendMode(blendMode); } } } @@ -718,10 +718,7 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback @Override public int getOpacity() { - if (mLayerState.mOpacityOverride != PixelFormat.UNKNOWN) { - return mLayerState.mOpacityOverride; - } - return mLayerState.getOpacity(); + return PixelFormat.TRANSLUCENT; } @Override diff --git a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java index 4f467d9aabae..82f587086428 100644 --- a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java @@ -19,6 +19,7 @@ package android.graphics.drawable; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UnsupportedAppUsage; import android.content.res.AssetFileDescriptor; import android.content.res.Resources; import android.content.res.Resources.Theme; @@ -290,8 +291,8 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { */ public AnimatedImageDrawable(long nativeImageDecoder, @Nullable ImageDecoder decoder, int width, int height, - int srcDensity, int dstDensity, Rect cropRect, - InputStream inputStream, AssetFileDescriptor afd) + long colorSpaceHandle, boolean extended, int srcDensity, int dstDensity, + Rect cropRect, InputStream inputStream, AssetFileDescriptor afd) throws IOException { width = Bitmap.scaleFromDensity(width, srcDensity, dstDensity); height = Bitmap.scaleFromDensity(height, srcDensity, dstDensity); @@ -308,11 +309,11 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { mIntrinsicHeight = cropRect.height(); } - mState = new State(nCreate(nativeImageDecoder, decoder, width, height, cropRect), - inputStream, afd); + mState = new State(nCreate(nativeImageDecoder, decoder, width, height, colorSpaceHandle, + extended, cropRect), inputStream, afd); final long nativeSize = nNativeByteSize(mState.mNativePtr); - NativeAllocationRegistry registry = new NativeAllocationRegistry( + NativeAllocationRegistry registry = NativeAllocationRegistry.createMalloced( AnimatedImageDrawable.class.getClassLoader(), nGetNativeFinalizer(), nativeSize); registry.registerNativeAllocation(mState, mState.mNativePtr); } @@ -562,6 +563,7 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { * callback, so no need to post. */ @SuppressWarnings("unused") + @UnsupportedAppUsage private void onAnimationEnd() { if (mAnimationCallbacks != null) { for (Animatable2.AnimationCallback callback : mAnimationCallbacks) { @@ -572,8 +574,8 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { private static native long nCreate(long nativeImageDecoder, - @Nullable ImageDecoder decoder, int width, int height, Rect cropRect) - throws IOException; + @Nullable ImageDecoder decoder, int width, int height, long colorSpaceHandle, + boolean extended, Rect cropRect) throws IOException; @FastNative private static native long nGetNativeFinalizer(); private static native long nDraw(long nativePtr, long canvasNativePtr); diff --git a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java index d714ca830976..b29fd4db5803 100644 --- a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java @@ -18,6 +18,7 @@ package android.graphics.drawable; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UnsupportedAppUsage; import android.graphics.Canvas; import android.graphics.Rect; import android.content.res.Resources; @@ -202,11 +203,13 @@ public class AnimatedRotateDrawable extends DrawableWrapper implements Animatabl R.styleable.AnimatedRotateDrawable_frameDuration, state.mFrameDuration)); } + @UnsupportedAppUsage public void setFramesCount(int framesCount) { mState.mFramesCount = framesCount; mIncrement = 360.0f / mState.mFramesCount; } + @UnsupportedAppUsage public void setFramesDuration(int framesDuration) { mState.mFrameDuration = framesDuration; } diff --git a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java index 3ed6a788b640..11a46c4ba9b9 100644 --- a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java @@ -20,9 +20,11 @@ import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UnsupportedAppUsage; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; +import android.os.Build; import android.util.AttributeSet; import android.util.Log; import android.util.LongSparseLongArray; @@ -66,6 +68,7 @@ public class AnimatedStateListDrawable extends StateListDrawable { private static final String ELEMENT_TRANSITION = "transition"; private static final String ELEMENT_ITEM = "item"; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private AnimatedStateListState mState; /** The currently running transition, if any. */ @@ -558,7 +561,9 @@ public class AnimatedStateListDrawable extends StateListDrawable { int[] mAnimThemeAttrs; + @UnsupportedAppUsage LongSparseLongArray mTransitions; + @UnsupportedAppUsage SparseIntArray mStateIds; AnimatedStateListState(@Nullable AnimatedStateListState orig, diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java index e397ed90c2e2..66947da9166f 100644 --- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java @@ -25,6 +25,7 @@ import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UnsupportedAppUsage; import android.app.ActivityThread; import android.app.Application; import android.content.pm.ActivityInfo.Config; @@ -32,14 +33,17 @@ import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; +import android.graphics.BlendMode; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Insets; import android.graphics.Outline; import android.graphics.PixelFormat; -import android.graphics.PorterDuff; +import android.graphics.RecordingCanvas; import android.graphics.Rect; +import android.graphics.RenderNode; import android.os.Build; +import android.os.Handler; import android.util.ArrayMap; import android.util.AttributeSet; import android.util.IntArray; @@ -49,8 +53,7 @@ import android.util.PathParser; import android.util.Property; import android.util.TimeUtils; import android.view.Choreographer; -import android.view.DisplayListCanvas; -import android.view.RenderNode; +import android.view.NativeVectorDrawableAnimator; import android.view.RenderNodeAnimatorSetHelper; import android.view.View; @@ -305,6 +308,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { private static final boolean DBG_ANIMATION_VECTOR_DRAWABLE = false; /** Local, mutable animator set. */ + @UnsupportedAppUsage private VectorDrawableAnimator mAnimatorSet; /** @@ -313,6 +317,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { */ private Resources mRes; + @UnsupportedAppUsage private AnimatedVectorDrawableState mAnimatedVectorState; /** The animator set that is parsed from the xml. */ @@ -472,8 +477,8 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { } @Override - public void setTintMode(PorterDuff.Mode tintMode) { - mAnimatedVectorState.mVectorDrawable.setTintMode(tintMode); + public void setTintBlendMode(@NonNull BlendMode blendMode) { + mAnimatedVectorState.mVectorDrawable.setTintBlendMode(blendMode); } @Override @@ -516,7 +521,6 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { mAnimatedVectorState.mVectorDrawable.getOutline(outline); } - /** @hide */ @Override public Insets getOpticalInsets() { return mAnimatedVectorState.mVectorDrawable.getOpticalInsets(); @@ -642,6 +646,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { * Force to animate on UI thread. * @hide */ + @UnsupportedAppUsage public void forceAnimationOnUI() { if (mAnimatorSet instanceof VectorDrawableAnimatorRT) { VectorDrawableAnimatorRT animator = (VectorDrawableAnimatorRT) mAnimatorSet; @@ -1228,7 +1233,8 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { /** * @hide */ - public static class VectorDrawableAnimatorRT implements VectorDrawableAnimator { + public static class VectorDrawableAnimatorRT implements VectorDrawableAnimator, + NativeVectorDrawableAnimator { private static final int START_ANIMATION = 1; private static final int REVERSE_ANIMATION = 2; private static final int RESET_ANIMATION = 3; @@ -1236,6 +1242,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { // If the duration of an animation is more than 300 frames, we cap the sample size to 300. private static final int MAX_SAMPLE_POINTS = 300; + private Handler mHandler; private AnimatorListener mListener = null; private final LongArray mStartDelays = new LongArray(); private PropertyValuesHolder.PropertyValues mTmpValues = @@ -1246,7 +1253,6 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { private boolean mInitialized = false; private boolean mIsReversible = false; private boolean mIsInfinite = false; - // TODO: Consider using NativeAllocationRegistery to track native allocation private final VirtualRefBasePtr mSetRefBasePtr; private WeakReference<RenderNode> mLastSeenTarget = null; private int mLastListenerId = 0; @@ -1537,11 +1543,11 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { } /** - * Holds a weak reference to the target that was last seen (through the DisplayListCanvas + * Holds a weak reference to the target that was last seen (through the RecordingCanvas * in the last draw call), so that when animator set needs to start, we can add the animator * to the last seen RenderNode target and start right away. */ - protected void recordLastSeenTarget(DisplayListCanvas canvas) { + protected void recordLastSeenTarget(RecordingCanvas canvas) { final RenderNode node = RenderNodeAnimatorSetHelper.getTarget(canvas); mLastSeenTarget = new WeakReference<RenderNode>(node); // Add the animator to the list of animators on every draw @@ -1666,6 +1672,9 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { .mRootName); } mStarted = true; + if (mHandler == null) { + mHandler = new Handler(); + } nStart(mSetPtr, this, ++mLastListenerId); invalidateOwningView(); if (mListener != null) { @@ -1701,6 +1710,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { } } + @Override public long getAnimatorNativePtr() { return mSetPtr; } @@ -1736,7 +1746,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { @Override public void onDraw(Canvas canvas) { if (canvas.isHardwareAccelerated()) { - recordLastSeenTarget((DisplayListCanvas) canvas); + recordLastSeenTarget((RecordingCanvas) canvas); } } @@ -1772,8 +1782,9 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { } // onFinished: should be called from native + @UnsupportedAppUsage private static void callOnFinished(VectorDrawableAnimatorRT set, int id) { - set.onAnimationEnd(id); + set.mHandler.post(() -> set.onAnimationEnd(id)); } private void transferPendingActions(VectorDrawableAnimator animatorSet) { diff --git a/graphics/java/android/graphics/drawable/AnimationDrawable.java b/graphics/java/android/graphics/drawable/AnimationDrawable.java index 0fd1741610ec..57764c2cb693 100644 --- a/graphics/java/android/graphics/drawable/AnimationDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimationDrawable.java @@ -24,6 +24,7 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import android.annotation.NonNull; +import android.annotation.UnsupportedAppUsage; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.Resources.Theme; @@ -88,6 +89,7 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An private AnimationState mAnimationState; /** The current frame, ranging from 0 to {@link #mAnimationState#getChildCount() - 1} */ + @UnsupportedAppUsage private int mCurFrame = 0; /** Whether the drawable has an animation callback posted. */ diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java index 44b783bb6e63..e4aa774fd434 100644 --- a/graphics/java/android/graphics/drawable/BitmapDrawable.java +++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java @@ -17,14 +17,16 @@ package android.graphics.drawable; import android.annotation.NonNull; +import android.annotation.UnsupportedAppUsage; import android.content.pm.ActivityInfo.Config; import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.graphics.BitmapShader; +import android.graphics.BlendMode; +import android.graphics.BlendModeColorFilter; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.ImageDecoder; @@ -33,9 +35,7 @@ import android.graphics.Matrix; import android.graphics.Outline; import android.graphics.Paint; import android.graphics.PixelFormat; -import android.graphics.PorterDuff; import android.graphics.PorterDuff.Mode; -import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; import android.graphics.Shader; import android.graphics.Xfermode; @@ -88,9 +88,11 @@ public class BitmapDrawable extends Drawable { private final Rect mDstRect = new Rect(); // #updateDstRectAndInsetsIfDirty() sets this + @UnsupportedAppUsage private BitmapState mBitmapState; - private PorterDuffColorFilter mTintFilter; + private BlendModeColorFilter mBlendModeFilter; + @UnsupportedAppUsage private int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT; private boolean mDstRectAndInsetsDirty = true; @@ -238,6 +240,7 @@ public class BitmapDrawable extends Drawable { } /** @hide */ + @UnsupportedAppUsage public void setBitmap(Bitmap bitmap) { if (mBitmapState.mBitmap != bitmap) { mBitmapState.mBitmap = bitmap; @@ -524,8 +527,8 @@ public class BitmapDrawable extends Drawable { } final boolean clearColorFilter; - if (mTintFilter != null && paint.getColorFilter() == null) { - paint.setColorFilter(mTintFilter); + if (mBlendModeFilter != null && paint.getColorFilter() == null) { + paint.setColorFilter(mBlendModeFilter); clearColorFilter = true; } else { clearColorFilter = false; @@ -627,9 +630,6 @@ public class BitmapDrawable extends Drawable { mDstRectAndInsetsDirty = false; } - /** - * @hide - */ @Override public Insets getOpticalInsets() { updateDstRectAndInsetsIfDirty(); @@ -678,17 +678,19 @@ public class BitmapDrawable extends Drawable { final BitmapState state = mBitmapState; if (state.mTint != tint) { state.mTint = tint; - mTintFilter = updateTintFilter(mTintFilter, tint, mBitmapState.mTintMode); + mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, tint, + mBitmapState.mBlendMode); invalidateSelf(); } } @Override - public void setTintMode(PorterDuff.Mode tintMode) { + public void setTintBlendMode(@NonNull BlendMode blendMode) { final BitmapState state = mBitmapState; - if (state.mTintMode != tintMode) { - state.mTintMode = tintMode; - mTintFilter = updateTintFilter(mTintFilter, mBitmapState.mTint, tintMode); + if (state.mBlendMode != blendMode) { + state.mBlendMode = blendMode; + mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, mBitmapState.mTint, + blendMode); invalidateSelf(); } } @@ -696,6 +698,7 @@ public class BitmapDrawable extends Drawable { /** * @hide only needed by a hack within ProgressBar */ + @UnsupportedAppUsage public ColorStateList getTint() { return mBitmapState.mTint; } @@ -703,8 +706,9 @@ public class BitmapDrawable extends Drawable { /** * @hide only needed by a hack within ProgressBar */ + @UnsupportedAppUsage public Mode getTintMode() { - return mBitmapState.mTintMode; + return BlendMode.blendModeToPorterDuffMode(mBitmapState.mBlendMode); } /** @@ -742,8 +746,9 @@ public class BitmapDrawable extends Drawable { @Override protected boolean onStateChange(int[] stateSet) { final BitmapState state = mBitmapState; - if (state.mTint != null && state.mTintMode != null) { - mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); + if (state.mTint != null && state.mBlendMode != null) { + mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, state.mTint, + state.mBlendMode); return true; } return false; @@ -862,7 +867,7 @@ public class BitmapDrawable extends Drawable { final int tintMode = a.getInt(R.styleable.BitmapDrawable_tintMode, -1); if (tintMode != -1) { - state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN); + state.mBlendMode = Drawable.parseBlendMode(tintMode, BlendMode.SRC_IN); } final ColorStateList tint = a.getColorStateList(R.styleable.BitmapDrawable_tint); @@ -977,7 +982,8 @@ public class BitmapDrawable extends Drawable { int[] mThemeAttrs = null; Bitmap mBitmap = null; ColorStateList mTint = null; - Mode mTintMode = DEFAULT_TINT_MODE; + BlendMode mBlendMode = DEFAULT_BLEND_MODE; + int mGravity = Gravity.FILL; float mBaseAlpha = 1.0f; Shader.TileMode mTileModeX = null; @@ -1003,7 +1009,7 @@ public class BitmapDrawable extends Drawable { BitmapState(BitmapState bitmapState) { mBitmap = bitmapState.mBitmap; mTint = bitmapState.mTint; - mTintMode = bitmapState.mTintMode; + mBlendMode = bitmapState.mBlendMode; mThemeAttrs = bitmapState.mThemeAttrs; mChangingConfigurations = bitmapState.mChangingConfigurations; mGravity = bitmapState.mGravity; @@ -1063,7 +1069,8 @@ public class BitmapDrawable extends Drawable { */ private void updateLocalState(Resources res) { mTargetDensity = resolveDensity(res, mBitmapState.mTargetDensity); - mTintFilter = updateTintFilter(mTintFilter, mBitmapState.mTint, mBitmapState.mTintMode); + mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, mBitmapState.mTint, + mBitmapState.mBlendMode); computeBitmapSize(); } } diff --git a/graphics/java/android/graphics/drawable/ClipDrawable.java b/graphics/java/android/graphics/drawable/ClipDrawable.java index d925b6b95c66..31fdb025bbc5 100644 --- a/graphics/java/android/graphics/drawable/ClipDrawable.java +++ b/graphics/java/android/graphics/drawable/ClipDrawable.java @@ -23,6 +23,7 @@ import org.xmlpull.v1.XmlPullParserException; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UnsupportedAppUsage; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.Resources.Theme; @@ -58,6 +59,7 @@ public class ClipDrawable extends DrawableWrapper { private final Rect mTmpRect = new Rect(); + @UnsupportedAppUsage private ClipState mState; ClipDrawable() { diff --git a/graphics/java/android/graphics/drawable/ColorDrawable.java b/graphics/java/android/graphics/drawable/ColorDrawable.java index 9ae747de2f82..f5fa8c546bed 100644 --- a/graphics/java/android/graphics/drawable/ColorDrawable.java +++ b/graphics/java/android/graphics/drawable/ColorDrawable.java @@ -20,13 +20,20 @@ import android.annotation.ColorInt; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; +import android.annotation.UnsupportedAppUsage; import android.content.pm.ActivityInfo.Config; -import android.graphics.*; -import android.graphics.PorterDuff.Mode; import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; +import android.graphics.BlendMode; +import android.graphics.BlendModeColorFilter; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Outline; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Xfermode; import android.util.AttributeSet; import android.view.ViewDebug; @@ -46,11 +53,12 @@ import java.io.IOException; * @attr ref android.R.styleable#ColorDrawable_color */ public class ColorDrawable extends Drawable { + @UnsupportedAppUsage private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); @ViewDebug.ExportedProperty(deepExport = true, prefix = "state_") private ColorState mColorState; - private PorterDuffColorFilter mTintFilter; + private BlendModeColorFilter mBlendModeColorFilter; private boolean mMutated; @@ -103,9 +111,10 @@ public class ColorDrawable extends Drawable { @Override public void draw(Canvas canvas) { final ColorFilter colorFilter = mPaint.getColorFilter(); - if ((mColorState.mUseColor >>> 24) != 0 || colorFilter != null || mTintFilter != null) { + if ((mColorState.mUseColor >>> 24) != 0 || colorFilter != null + || mBlendModeColorFilter != null) { if (colorFilter == null) { - mPaint.setColorFilter(mTintFilter); + mPaint.setColorFilter(mBlendModeColorFilter); } mPaint.setColor(mColorState.mUseColor); @@ -141,9 +150,13 @@ public class ColorDrawable extends Drawable { } /** - * Returns the alpha value of this drawable's color. + * Returns the alpha value of this drawable's color. Note this may not be the same alpha value + * provided in {@link Drawable#setAlpha(int)}. Instead this will return the alpha of the color + * combined with the alpha provided by setAlpha * * @return A value between 0 and 255. + * + * @see ColorDrawable#setAlpha(int) */ @Override public int getAlpha() { @@ -151,7 +164,9 @@ public class ColorDrawable extends Drawable { } /** - * Sets the color's alpha value. + * Applies the given alpha to the underlying color. Note if the color already has + * an alpha applied to it, this will apply this alpha to the existing value instead of + * overwriting it. * * @param alpha The alpha value to set, between 0 and 255. */ @@ -180,25 +195,39 @@ public class ColorDrawable extends Drawable { mPaint.setColorFilter(colorFilter); } + /** + * Returns the color filter applied to this color configured by + * {@link #setColorFilter(ColorFilter)} + * + * @see android.graphics.drawable.Drawable#getColorFilter() + */ + @Override + public @Nullable ColorFilter getColorFilter() { + return mPaint.getColorFilter(); + } + @Override public void setTintList(ColorStateList tint) { mColorState.mTint = tint; - mTintFilter = updateTintFilter(mTintFilter, tint, mColorState.mTintMode); + mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, tint, + mColorState.mBlendMode); invalidateSelf(); } @Override - public void setTintMode(Mode tintMode) { - mColorState.mTintMode = tintMode; - mTintFilter = updateTintFilter(mTintFilter, mColorState.mTint, tintMode); + public void setTintBlendMode(@NonNull BlendMode blendMode) { + mColorState.mBlendMode = blendMode; + mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, mColorState.mTint, + blendMode); invalidateSelf(); } @Override protected boolean onStateChange(int[] stateSet) { final ColorState state = mColorState; - if (state.mTint != null && state.mTintMode != null) { - mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); + if (state.mTint != null && state.mBlendMode != null) { + mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, state.mTint, + state.mBlendMode); return true; } return false; @@ -236,7 +265,7 @@ public class ColorDrawable extends Drawable { @Override public int getOpacity() { - if (mTintFilter != null || mPaint.getColorFilter() != null) { + if (mBlendModeColorFilter != null || mPaint.getColorFilter() != null) { return PixelFormat.TRANSLUCENT; } @@ -319,10 +348,11 @@ public class ColorDrawable extends Drawable { int[] mThemeAttrs; int mBaseColor; // base color, independent of setAlpha() @ViewDebug.ExportedProperty + @UnsupportedAppUsage int mUseColor; // basecolor modulated by setAlpha() @Config int mChangingConfigurations; ColorStateList mTint = null; - Mode mTintMode = DEFAULT_TINT_MODE; + BlendMode mBlendMode = DEFAULT_BLEND_MODE; ColorState() { // Empty constructor. @@ -334,7 +364,7 @@ public class ColorDrawable extends Drawable { mUseColor = state.mUseColor; mChangingConfigurations = state.mChangingConfigurations; mTint = state.mTint; - mTintMode = state.mTintMode; + mBlendMode = state.mBlendMode; } @Override @@ -372,6 +402,7 @@ public class ColorDrawable extends Drawable { * after inflating or applying a theme. */ private void updateLocalState(Resources r) { - mTintFilter = updateTintFilter(mTintFilter, mColorState.mTint, mColorState.mTintMode); + mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, mColorState.mTint, + mColorState.mBlendMode); } } diff --git a/graphics/java/android/graphics/drawable/ColorStateListDrawable.java b/graphics/java/android/graphics/drawable/ColorStateListDrawable.java new file mode 100644 index 000000000000..20cd825fe306 --- /dev/null +++ b/graphics/java/android/graphics/drawable/ColorStateListDrawable.java @@ -0,0 +1,306 @@ +/* + * Copyright 2018 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.graphics.drawable; + +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.pm.ActivityInfo; +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.graphics.BlendMode; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.util.MathUtils; + +/** + * A Drawable that manages a {@link ColorDrawable} to make it stateful and backed by a + * {@link ColorStateList}. + */ +public class ColorStateListDrawable extends Drawable implements Drawable.Callback { + private ColorDrawable mColorDrawable; + private ColorStateListDrawableState mState; + private boolean mMutated = false; + + public ColorStateListDrawable() { + mState = new ColorStateListDrawableState(); + initializeColorDrawable(); + } + + public ColorStateListDrawable(@NonNull ColorStateList colorStateList) { + mState = new ColorStateListDrawableState(); + initializeColorDrawable(); + setColorStateList(colorStateList); + } + + @Override + public void draw(@NonNull Canvas canvas) { + mColorDrawable.draw(canvas); + } + + @Override + @IntRange(from = 0, to = 255) + public int getAlpha() { + return mColorDrawable.getAlpha(); + } + + @Override + public boolean isStateful() { + return mState.isStateful(); + } + + @Override + public boolean hasFocusStateSpecified() { + return mState.hasFocusStateSpecified(); + } + + @Override + public @NonNull Drawable getCurrent() { + return mColorDrawable; + } + + @Override + public void applyTheme(@NonNull Resources.Theme t) { + super.applyTheme(t); + + if (mState.mColor != null) { + setColorStateList(mState.mColor.obtainForTheme(t)); + } + + if (mState.mTint != null) { + setTintList(mState.mTint.obtainForTheme(t)); + } + } + + @Override + public boolean canApplyTheme() { + return super.canApplyTheme() || mState.canApplyTheme(); + } + + @Override + public void setAlpha(@IntRange(from = 0, to = 255) int alpha) { + mState.mAlpha = alpha; + onStateChange(getState()); + } + + /** + * Remove the alpha override, reverting to the alpha defined on each color in the + * {@link ColorStateList}. + */ + public void clearAlpha() { + mState.mAlpha = -1; + onStateChange(getState()); + } + + @Override + public void setTintList(@Nullable ColorStateList tint) { + mState.mTint = tint; + mColorDrawable.setTintList(tint); + onStateChange(getState()); + } + + @Override + public void setTintBlendMode(@NonNull BlendMode blendMode) { + mState.mBlendMode = blendMode; + mColorDrawable.setTintBlendMode(blendMode); + onStateChange(getState()); + } + + @Override + public @Nullable ColorFilter getColorFilter() { + return mColorDrawable.getColorFilter(); + } + + @Override + public void setColorFilter(@Nullable ColorFilter colorFilter) { + mColorDrawable.setColorFilter(colorFilter); + } + + @Override + public @PixelFormat.Opacity int getOpacity() { + return mColorDrawable.getOpacity(); + } + + @Override + protected void onBoundsChange(Rect bounds) { + super.onBoundsChange(bounds); + mColorDrawable.setBounds(bounds); + } + + @Override + protected boolean onStateChange(int[] state) { + if (mState.mColor != null) { + int color = mState.mColor.getColorForState(state, mState.mColor.getDefaultColor()); + + if (mState.mAlpha != -1) { + color = (color & 0xFFFFFF) | MathUtils.constrain(mState.mAlpha, 0, 255) << 24; + } + + if (color != mColorDrawable.getColor()) { + mColorDrawable.setColor(color); + mColorDrawable.setState(state); + return true; + } else { + return mColorDrawable.setState(state); + } + } else { + return false; + } + } + + @Override + public void invalidateDrawable(@NonNull Drawable who) { + if (who == mColorDrawable && getCallback() != null) { + getCallback().invalidateDrawable(this); + } + } + + @Override + public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) { + if (who == mColorDrawable && getCallback() != null) { + getCallback().scheduleDrawable(this, what, when); + } + } + + @Override + public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) { + if (who == mColorDrawable && getCallback() != null) { + getCallback().unscheduleDrawable(this, what); + } + } + + @Override + public @NonNull ConstantState getConstantState() { + mState.mChangingConfigurations = mState.mChangingConfigurations + | (getChangingConfigurations() & ~mState.getChangingConfigurations()); + return mState; + } + + /** + * Returns the ColorStateList backing this Drawable, or a new ColorStateList of the default + * ColorDrawable color if one hasn't been defined yet. + * + * @return a ColorStateList + */ + public @NonNull ColorStateList getColorStateList() { + if (mState.mColor == null) { + return ColorStateList.valueOf(mColorDrawable.getColor()); + } else { + return mState.mColor; + } + } + + @Override + public int getChangingConfigurations() { + return super.getChangingConfigurations() | mState.getChangingConfigurations(); + } + + @Override + public @NonNull Drawable mutate() { + if (!mMutated && super.mutate() == this) { + mState = new ColorStateListDrawableState(mState); + mMutated = true; + } + return this; + } + + /** + * @hide + */ + @Override + public void clearMutated() { + super.clearMutated(); + mMutated = false; + } + + /** + * Replace this Drawable's ColorStateList. It is not copied, so changes will propagate on the + * next call to {@link #setState(int[])}. + * + * @param colorStateList A color state list to attach. + */ + public void setColorStateList(@NonNull ColorStateList colorStateList) { + mState.mColor = colorStateList; + onStateChange(getState()); + } + + static final class ColorStateListDrawableState extends ConstantState { + ColorStateList mColor = null; + ColorStateList mTint = null; + int mAlpha = -1; + BlendMode mBlendMode = DEFAULT_BLEND_MODE; + @ActivityInfo.Config int mChangingConfigurations = 0; + + ColorStateListDrawableState() { + } + + ColorStateListDrawableState(ColorStateListDrawableState state) { + mColor = state.mColor; + mTint = state.mTint; + mAlpha = state.mAlpha; + mBlendMode = state.mBlendMode; + mChangingConfigurations = state.mChangingConfigurations; + } + + @Override + public Drawable newDrawable() { + return new ColorStateListDrawable(this); + } + + @Override + public @ActivityInfo.Config int getChangingConfigurations() { + return mChangingConfigurations + | (mColor != null ? mColor.getChangingConfigurations() : 0) + | (mTint != null ? mTint.getChangingConfigurations() : 0); + } + + public boolean isStateful() { + return (mColor != null && mColor.isStateful()) + || (mTint != null && mTint.isStateful()); + } + + public boolean hasFocusStateSpecified() { + return (mColor != null && mColor.hasFocusStateSpecified()) + || (mTint != null && mTint.hasFocusStateSpecified()); + } + + @Override + public boolean canApplyTheme() { + return (mColor != null && mColor.canApplyTheme()) + || (mTint != null && mTint.canApplyTheme()); + } + } + + private ColorStateListDrawable(@NonNull ColorStateListDrawableState state) { + mState = state; + initializeColorDrawable(); + } + + private void initializeColorDrawable() { + mColorDrawable = new ColorDrawable(); + mColorDrawable.setCallback(this); + + if (mState.mTint != null) { + mColorDrawable.setTintList(mState.mTint); + } + + if (mState.mBlendMode != DEFAULT_BLEND_MODE) { + mColorDrawable.setTintBlendMode(mState.mBlendMode); + } + } +} diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java index 8af2fd8bbb5e..08409869c626 100644 --- a/graphics/java/android/graphics/drawable/Drawable.java +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -16,17 +16,13 @@ package android.graphics.drawable; -import com.android.internal.R; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - import android.annotation.AttrRes; import android.annotation.ColorInt; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; +import android.annotation.UnsupportedAppUsage; import android.content.pm.ActivityInfo.Config; import android.content.res.ColorStateList; import android.content.res.Resources; @@ -34,6 +30,8 @@ import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.BlendMode; +import android.graphics.BlendModeColorFilter; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; @@ -57,6 +55,11 @@ import android.util.TypedValue; import android.util.Xml; import android.view.View; +import com.android.internal.R; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; @@ -181,11 +184,13 @@ public abstract class Drawable { private static final Rect ZERO_BOUNDS_RECT = new Rect(); static final PorterDuff.Mode DEFAULT_TINT_MODE = PorterDuff.Mode.SRC_IN; + static final BlendMode DEFAULT_BLEND_MODE = BlendMode.SRC_IN; private int[] mStateSet = StateSet.WILD_CARD; private int mLevel = 0; private @Config int mChangingConfigurations = 0; private Rect mBounds = ZERO_BOUNDS_RECT; // lazily becomes a new Rect() + @UnsupportedAppUsage private WeakReference<Callback> mCallback = null; private boolean mVisible = true; @@ -204,9 +209,28 @@ public abstract class Drawable { * * @hide */ + @UnsupportedAppUsage protected int mSrcDensityOverride = 0; /** + * Flag used to break the recursive loop between setTintBlendMode(PorterDuff.Mode) and + * setTintBlendMode(BlendMode) as each default implementation invokes the other in order to + * support new use cases that utilize the new blending modes as well as support the legacy + * use cases. This flag tracks that {@link #setTintBlendMode(BlendMode)} is only invoked once + * per invocation. + */ + private boolean mSetBlendModeInvoked = false; + + /** + * Flag used to break the recursive loop between setTintBlendMode(PorterDuff.Mode) and + * setTintBlendMode(BlendMode) as each default implementation invokes the other in order to + * support new use cases that utilize the new blending modes as well as support the legacy + * use cases. This flag tracks that {@link #setTintMode(Mode)} is only invoked once + * per invocation; + */ + private boolean mSetTintModeInvoked = false; + + /** * Draw in its bounds (set via setBounds) respecting optional effects such * as alpha (set via setAlpha) and color filter (set via setColorFilter). * @@ -592,7 +616,12 @@ public abstract class Drawable { * <p class="note"><strong>Note:</strong> Setting a color filter disables * {@link #setTintList(ColorStateList) tint}. * </p> + * + * @see {@link #setColorFilter(ColorFilter)} } + * @deprecated use {@link #setColorFilter(ColorFilter)} with an instance + * of {@link android.graphics.BlendModeColorFilter} */ + @Deprecated public void setColorFilter(@ColorInt int color, @NonNull PorterDuff.Mode mode) { if (getColorFilter() instanceof PorterDuffColorFilter) { PorterDuffColorFilter existing = (PorterDuffColorFilter) getColorFilter(); @@ -622,6 +651,7 @@ public abstract class Drawable { * @param tintColor Color to use for tinting this drawable * @see #setTintList(ColorStateList) * @see #setTintMode(PorterDuff.Mode) + * @see #setTintBlendMode(BlendMode) */ public void setTint(@ColorInt int tintColor) { setTintList(ColorStateList.valueOf(tintColor)); @@ -643,6 +673,7 @@ public abstract class Drawable { * {@code null} to clear the tint * @see #setTint(int) * @see #setTintMode(PorterDuff.Mode) + * @see #setTintBlendMode(BlendMode) */ public void setTintList(@Nullable ColorStateList tint) {} @@ -657,11 +688,44 @@ public abstract class Drawable { * {@link #setColorFilter(int, PorterDuff.Mode)} overrides tint. * </p> * - * @param tintMode A Porter-Duff blending mode + * @param tintMode A Porter-Duff blending mode to apply to the drawable, a value of null sets + * the default Porter-Diff blending mode value + * of {@link PorterDuff.Mode#SRC_IN} * @see #setTint(int) * @see #setTintList(ColorStateList) */ - public void setTintMode(@NonNull PorterDuff.Mode tintMode) {} + public void setTintMode(@Nullable PorterDuff.Mode tintMode) { + if (!mSetTintModeInvoked) { + mSetTintModeInvoked = true; + BlendMode mode = tintMode != null ? BlendMode.fromValue(tintMode.nativeInt) : null; + setTintBlendMode(mode != null ? mode : Drawable.DEFAULT_BLEND_MODE); + mSetTintModeInvoked = false; + } + } + + /** + * Specifies a tint blending mode for this drawable. + * <p> + * Defines how this drawable's tint color should be blended into the drawable + * before it is drawn to screen. Default tint mode is {@link BlendMode#SRC_IN}. + * </p> + * <p class="note"><strong>Note:</strong> Setting a color filter via + * {@link #setColorFilter(ColorFilter)} + * </p> + * + * @param blendMode BlendMode to apply to the drawable, a value of null sets the default + * blend mode value of {@link BlendMode#SRC_IN} + * @see #setTint(int) + * @see #setTintList(ColorStateList) + */ + public void setTintBlendMode(@Nullable BlendMode blendMode) { + if (!mSetBlendModeInvoked) { + mSetBlendModeInvoked = true; + PorterDuff.Mode mode = BlendMode.blendModeToPorterDuffMode(blendMode); + setTintMode(mode != null ? mode : Drawable.DEFAULT_TINT_MODE); + mSetBlendModeInvoked = false; + } + } /** * Returns the current color filter, or {@code null} if none set. @@ -710,9 +774,11 @@ public abstract class Drawable { } /** - * Whether this drawable requests projection. + * Whether this drawable requests projection. Indicates that the + * {@link android.graphics.RenderNode} this Drawable will draw into should be drawn immediately + * after the closest ancestor RenderNode containing a projection receiver. * - * @hide magic! + * @see android.graphics.RenderNode#setProjectBackwards(boolean) */ public boolean isProjected() { return false; @@ -930,11 +996,13 @@ public abstract class Drawable { * do account for the value of {@link #setAlpha}, but the general behavior is dependent * upon the implementation of the subclass. * + * @deprecated This method is no longer used in graphics optimizations + * * @return int The opacity class of the Drawable. * * @see android.graphics.PixelFormat */ - public abstract @PixelFormat.Opacity int getOpacity(); + @Deprecated public abstract @PixelFormat.Opacity int getOpacity(); /** * Return the appropriate opacity value for two source opacities. If @@ -1088,7 +1156,6 @@ public abstract class Drawable { * Return in insets the layout insets suggested by this Drawable for use with alignment * operations during layout. * - * @hide */ public @NonNull Insets getOpticalInsets() { return Insets.NONE; @@ -1234,6 +1301,9 @@ public abstract class Drawable { return ImageDecoder.decodeDrawable(source, (decoder, info, src) -> { decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE); + decoder.setOnPartialImageListener((e) -> { + return e.getError() == ImageDecoder.DecodeException.SOURCE_INCOMPLETE; + }); }); } catch (IOException e) { /* do nothing. @@ -1390,6 +1460,7 @@ public abstract class Drawable { * @throws XmlPullParserException * @throws IOException */ + @UnsupportedAppUsage void inflateWithAttributes(@NonNull @SuppressWarnings("unused") Resources r, @NonNull @SuppressWarnings("unused") XmlPullParser parser, @NonNull TypedArray attrs, @AttrRes int visibleAttr) throws XmlPullParserException, IOException { @@ -1509,6 +1580,7 @@ public abstract class Drawable { * Ensures the tint filter is consistent with the current tint color and * mode. */ + @UnsupportedAppUsage @Nullable PorterDuffColorFilter updateTintFilter(@Nullable PorterDuffColorFilter tintFilter, @Nullable ColorStateList tint, @Nullable PorterDuff.Mode tintMode) { if (tint == null || tintMode == null) { @@ -1516,15 +1588,28 @@ public abstract class Drawable { } final int color = tint.getColorForState(getState(), Color.TRANSPARENT); - if (tintFilter == null) { + if (tintFilter == null || tintFilter.getColor() != color + || tintFilter.getMode() != tintMode) { return new PorterDuffColorFilter(color, tintMode); } - tintFilter.setColor(color); - tintFilter.setMode(tintMode); return tintFilter; } + @Nullable BlendModeColorFilter updateBlendModeFilter(@Nullable BlendModeColorFilter blendFilter, + @Nullable ColorStateList tint, @Nullable BlendMode blendMode) { + if (tint == null || blendMode == null) { + return null; + } + + final int color = tint.getColorForState(getState(), Color.TRANSPARENT); + if (blendFilter == null || blendFilter.getColor() != color + || blendFilter.getMode() != blendMode) { + return new BlendModeColorFilter(color, blendMode); + } + return blendFilter; + } + /** * Obtains styled attributes from the theme, if available, or unstyled * resources if the theme is null. @@ -1615,6 +1700,7 @@ public abstract class Drawable { * * @hide */ + @UnsupportedAppUsage public static PorterDuff.Mode parseTintMode(int value, Mode defaultMode) { switch (value) { case 3: return Mode.SRC_OVER; @@ -1626,5 +1712,26 @@ public abstract class Drawable { default: return defaultMode; } } + + /** + * Parses a {@link android.graphics.BlendMode} from a tintMode + * attribute's enum value. + * + * @hide + */ + @UnsupportedAppUsage + public static BlendMode parseBlendMode(int value, BlendMode defaultMode) { + switch (value) { + case 3: return BlendMode.SRC_OVER; + case 5: return BlendMode.SRC_IN; + case 9: return BlendMode.SRC_ATOP; + // b/73224934 PorterDuff Multiply maps to Skia Modulate so actually + // return BlendMode.MODULATE here + case 14: return BlendMode.MODULATE; + case 15: return BlendMode.SCREEN; + case 16: return BlendMode.PLUS; + default: return defaultMode; + } + } } diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java index aa4cd9cba4a7..090d915a2f67 100644 --- a/graphics/java/android/graphics/drawable/DrawableContainer.java +++ b/graphics/java/android/graphics/drawable/DrawableContainer.java @@ -17,17 +17,19 @@ package android.graphics.drawable; import android.annotation.NonNull; +import android.annotation.UnsupportedAppUsage; import android.content.pm.ActivityInfo.Config; import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.Resources.Theme; +import android.graphics.BlendMode; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Insets; import android.graphics.Outline; import android.graphics.PixelFormat; -import android.graphics.PorterDuff.Mode; import android.graphics.Rect; +import android.os.Build; import android.os.SystemClock; import android.util.DisplayMetrics; import android.util.LayoutDirection; @@ -54,9 +56,11 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { * to improve the quality at negligible cost. */ private static final boolean DEFAULT_DITHER = true; + @UnsupportedAppUsage private DrawableContainerState mDrawableContainerState; private Rect mHotspotBounds; private Drawable mCurrDrawable; + @UnsupportedAppUsage private Drawable mLastDrawable; private int mAlpha = 0xFF; @@ -120,9 +124,6 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { return result; } - /** - * @hide - */ @Override public Insets getOpticalInsets() { if (mCurrDrawable != null) { @@ -195,14 +196,14 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { } @Override - public void setTintMode(Mode tintMode) { + public void setTintBlendMode(@NonNull BlendMode blendMode) { mDrawableContainerState.mHasTintMode = true; - if (mDrawableContainerState.mTintMode != tintMode) { - mDrawableContainerState.mTintMode = tintMode; + if (mDrawableContainerState.mBlendMode != blendMode) { + mDrawableContainerState.mBlendMode = blendMode; if (mCurrDrawable != null) { - mCurrDrawable.setTintMode(tintMode); + mCurrDrawable.setTintBlendMode(blendMode); } } } @@ -543,7 +544,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { d.setTintList(mDrawableContainerState.mTintList); } if (mDrawableContainerState.mHasTintMode) { - d.setTintMode(mDrawableContainerState.mTintMode); + d.setTintBlendMode(mDrawableContainerState.mBlendMode); } } @@ -689,11 +690,13 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { @Config int mChildrenChangingConfigurations; SparseArray<ConstantState> mDrawableFutures; + @UnsupportedAppUsage Drawable[] mDrawables; int mNumChildren; boolean mVariablePadding = false; boolean mCheckedPadding; + @UnsupportedAppUsage Rect mConstantPadding; boolean mConstantSize = false; @@ -723,16 +726,18 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { boolean mAutoMirrored; ColorFilter mColorFilter; + @UnsupportedAppUsage boolean mHasColorFilter; ColorStateList mTintList; - Mode mTintMode; + BlendMode mBlendMode; boolean mHasTintList; boolean mHasTintMode; /** * @hide */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) protected DrawableContainerState(DrawableContainerState orig, DrawableContainer owner, Resources res) { mOwner = owner; @@ -757,7 +762,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { mColorFilter = orig.mColorFilter; mHasColorFilter = orig.mHasColorFilter; mTintList = orig.mTintList; - mTintMode = orig.mTintMode; + mBlendMode = orig.mBlendMode; mHasTintList = orig.mHasTintList; mHasTintMode = orig.mHasTintMode; diff --git a/graphics/java/android/graphics/drawable/DrawableInflater.java b/graphics/java/android/graphics/drawable/DrawableInflater.java index 0ee9071f4d06..bad3791a9c24 100644 --- a/graphics/java/android/graphics/drawable/DrawableInflater.java +++ b/graphics/java/android/graphics/drawable/DrawableInflater.java @@ -22,6 +22,7 @@ import org.xmlpull.v1.XmlPullParserException; import android.annotation.DrawableRes; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.Resources; import android.content.res.Resources.Theme; @@ -49,6 +50,7 @@ public final class DrawableInflater { new HashMap<>(); private final Resources mRes; + @UnsupportedAppUsage private final ClassLoader mClassLoader; /** diff --git a/graphics/java/android/graphics/drawable/DrawableWrapper.java b/graphics/java/android/graphics/drawable/DrawableWrapper.java index b71f3ef594a9..64fc7042dfc7 100644 --- a/graphics/java/android/graphics/drawable/DrawableWrapper.java +++ b/graphics/java/android/graphics/drawable/DrawableWrapper.java @@ -16,35 +16,38 @@ package android.graphics.drawable; -import com.android.internal.R; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UnsupportedAppUsage; import android.content.pm.ActivityInfo.Config; import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; +import android.graphics.BlendMode; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Insets; import android.graphics.Outline; import android.graphics.PixelFormat; -import android.graphics.PorterDuff; import android.graphics.Rect; +import android.graphics.Xfermode; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.view.View; +import com.android.internal.R; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + import java.io.IOException; /** * Drawable container with only one child element. */ public abstract class DrawableWrapper extends Drawable implements Drawable.Callback { + @UnsupportedAppUsage private DrawableWrapperState mState; private Drawable mDrawable; private boolean mMutated; @@ -62,7 +65,7 @@ public abstract class DrawableWrapper extends Drawable implements Drawable.Callb */ public DrawableWrapper(@Nullable Drawable dr) { mState = null; - mDrawable = dr; + setDrawable(dr); } /** @@ -78,6 +81,16 @@ public abstract class DrawableWrapper extends Drawable implements Drawable.Callb } /** + * @hide + */ + @Override + public void setXfermode(Xfermode mode) { + if (mDrawable != null) { + mDrawable.setXfermode(mode); + } + } + + /** * Sets the wrapped drawable. * * @param dr the wrapped drawable @@ -240,7 +253,6 @@ public abstract class DrawableWrapper extends Drawable implements Drawable.Callb return mDrawable != null && mDrawable.getPadding(padding); } - /** @hide */ @Override public Insets getOpticalInsets() { return mDrawable != null ? mDrawable.getOpticalInsets() : Insets.NONE; @@ -312,9 +324,9 @@ public abstract class DrawableWrapper extends Drawable implements Drawable.Callb } @Override - public void setTintMode(@Nullable PorterDuff.Mode tintMode) { + public void setTintBlendMode(@NonNull BlendMode blendMode) { if (mDrawable != null) { - mDrawable.setTintMode(tintMode); + mDrawable.setTintBlendMode(blendMode); } } diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java index dfdddb2a599a..b9945cc735d8 100644 --- a/graphics/java/android/graphics/drawable/GradientDrawable.java +++ b/graphics/java/android/graphics/drawable/GradientDrawable.java @@ -17,14 +17,19 @@ package android.graphics.drawable; import android.annotation.ColorInt; +import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.Px; +import android.annotation.UnsupportedAppUsage; import android.content.pm.ActivityInfo.Config; import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; +import android.graphics.BlendMode; +import android.graphics.BlendModeColorFilter; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; @@ -35,14 +40,13 @@ import android.graphics.Outline; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PixelFormat; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; import android.graphics.RadialGradient; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Shader; import android.graphics.SweepGradient; import android.graphics.Xfermode; +import android.os.Build; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; @@ -155,13 +159,17 @@ public class GradientDrawable extends Drawable { private static final float DEFAULT_INNER_RADIUS_RATIO = 3.0f; private static final float DEFAULT_THICKNESS_RATIO = 9.0f; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917) private GradientState mGradientState; + @UnsupportedAppUsage private final Paint mFillPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124051827) private Rect mPadding; + @UnsupportedAppUsage private Paint mStrokePaint; // optional, set by the caller private ColorFilter mColorFilter; // optional, set by the caller - private PorterDuffColorFilter mTintFilter; + private BlendModeColorFilter mBlendModeColorFilter; private int mAlpha = 0xFF; // modified by the caller private final Path mPath = new Path(); @@ -392,6 +400,7 @@ public class GradientDrawable extends Drawable { e = new DashPathEffect(new float[] { dashWidth, dashGap }, 0); } mStrokePaint.setPathEffect(e); + mGradientIsDirty = true; invalidateSelf(); } @@ -574,9 +583,9 @@ public class GradientDrawable extends Drawable { * The default value for this property is {@code false}. * <p> * <strong>Note</strong>: This property corresponds to the - * {@code android:useLevel} attribute on the inner {@code <gradient>} + * {@code android:useLevel} attribute on the inner {@code <gradient>} * tag, NOT the {@code android:useLevel} attribute on the outer - * {@code <shape>} tag. For example, + * {@code <shape>} tag. For example, * <pre>{@code * <shape ...> * <gradient @@ -628,7 +637,7 @@ public class GradientDrawable extends Drawable { * @see #setOrientation(Orientation) */ public Orientation getOrientation() { - return mGradientState.mOrientation; + return mGradientState.getOrientation(); } /** @@ -644,7 +653,7 @@ public class GradientDrawable extends Drawable { * @see #getOrientation() */ public void setOrientation(Orientation orientation) { - mGradientState.mOrientation = orientation; + mGradientState.setOrientation(orientation); mGradientIsDirty = true; invalidateSelf(); } @@ -663,8 +672,29 @@ public class GradientDrawable extends Drawable { * @see #mutate() * @see #setColor(int) */ - public void setColors(@ColorInt int[] colors) { + public void setColors(@Nullable @ColorInt int[] colors) { + setColors(colors, null); + } + + /** + * Sets the colors and offsets used to draw the gradient. + * <p> + * Each color is specified as an ARGB integer and the array must contain at + * least 2 colors. + * <p> + * <strong>Note</strong>: changing colors will affect all instances of a + * drawable loaded from a resource. It is recommended to invoke + * {@link #mutate()} before changing the colors. + * + * @param colors an array containing 2 or more ARGB colors + * @param offsets optional array of floating point parameters representing the positions + * of the colors. Null evenly disperses the colors + * @see #mutate() + * @see #setColors(int[]) + */ + public void setColors(@Nullable @ColorInt int[] colors, @Nullable float[] offsets) { mGradientState.setGradientColors(colors); + mGradientState.mPositions = offsets; mGradientIsDirty = true; invalidateSelf(); } @@ -701,7 +731,7 @@ public class GradientDrawable extends Drawable { mStrokePaint.getStrokeWidth() > 0; final boolean haveFill = currFillAlpha > 0; final GradientState st = mGradientState; - final ColorFilter colorFilter = mColorFilter != null ? mColorFilter : mTintFilter; + final ColorFilter colorFilter = mColorFilter != null ? mColorFilter : mBlendModeColorFilter; /* we need a layer iff we're drawing both a fill and stroke, and the stroke is non-opaque, and our shapetype actually supports @@ -843,6 +873,123 @@ public class GradientDrawable extends Drawable { } } + /** + * Inner radius of the ring expressed as a ratio of the ring's width. + * + * @see #getInnerRadiusRatio() + * @attr ref android.R.styleable#GradientDrawable_innerRadiusRatio + */ + public void setInnerRadiusRatio( + @FloatRange(from = 0.0f, fromInclusive = false) float innerRadiusRatio) { + if (innerRadiusRatio <= 0) { + throw new IllegalArgumentException("Ratio must be greater than zero"); + } + mGradientState.mInnerRadiusRatio = innerRadiusRatio; + mPathIsDirty = true; + invalidateSelf(); + } + + /** + * Return the inner radius of the ring expressed as a ratio of the ring's width. + * + * @see #setInnerRadiusRatio(float) + * @attr ref android.R.styleable#GradientDrawable_innerRadiusRatio + */ + public float getInnerRadiusRatio() { + return mGradientState.mInnerRadiusRatio; + } + + /** + * Configure the inner radius of the ring. + * + * @see #getInnerRadius() + * @attr ref android.R.styleable#GradientDrawable_innerRadius + */ + public void setInnerRadius(@Px int innerRadius) { + mGradientState.mInnerRadius = innerRadius; + mPathIsDirty = true; + invalidateSelf(); + } + + /** + * Retrn the inner radius of the ring + * + * @see #setInnerRadius(int) + * @attr ref android.R.styleable#GradientDrawable_innerRadius + */ + public @Px int getInnerRadius() { + return mGradientState.mInnerRadius; + } + + /** + * Configure the thickness of the ring expressed as a ratio of the ring's width. + * + * @see #getThicknessRatio() + * @attr ref android.R.styleable#GradientDrawable_thicknessRatio + */ + public void setThicknessRatio( + @FloatRange(from = 0.0f, fromInclusive = false) float thicknessRatio) { + if (thicknessRatio <= 0) { + throw new IllegalArgumentException("Ratio must be greater than zero"); + } + mGradientState.mThicknessRatio = thicknessRatio; + mPathIsDirty = true; + invalidateSelf(); + } + + /** + * Return the thickness ratio of the ring expressed as a ratio of the ring's width. + * + * @see #setThicknessRatio(float) + * @attr ref android.R.styleable#GradientDrawable_thicknessRatio + */ + public float getThicknessRatio() { + return mGradientState.mThicknessRatio; + } + + /** + * Configure the thickness of the ring. + * + * @attr ref android.R.styleable#GradientDrawable_thickness + */ + public void setThickness(@Px int thickness) { + mGradientState.mThickness = thickness; + mPathIsDirty = true; + invalidateSelf(); + } + + /** + * Return the thickness of the ring + * + * @see #setThickness(int) + * @attr ref android.R.styleable#GradientDrawable_thickness + */ + public @Px int getThickness() { + return mGradientState.mThickness; + } + + /** + * Configure the padding of the gradient shape + * @param left Left padding of the gradient shape + * @param top Top padding of the gradient shape + * @param right Right padding of the gradient shape + * @param bottom Bottom padding of the gradient shape + * + * @attr ref android.R.styleable#GradientDrawablePadding_left + * @attr ref android.R.styleable#GradientDrawablePadding_top + * @attr ref android.R.styleable#GradientDrawablePadding_right + * @attr ref android.R.styleable#GradientDrawablePadding_bottom + */ + public void setPadding(@Px int left, @Px int top, @Px int right, @Px int bottom) { + if (mGradientState.mPadding == null) { + mGradientState.mPadding = new Rect(); + } + + mGradientState.mPadding.set(left, top, right, bottom); + mPadding = mGradientState.mPadding; + invalidateSelf(); + } + private Path buildRing(GradientState st) { if (mRingPath != null && (!st.mUseLevelForShape || !mPathIsDirty)) return mRingPath; mPathIsDirty = false; @@ -930,16 +1077,15 @@ public class GradientDrawable extends Drawable { * @see #getColor */ public void setColor(@Nullable ColorStateList colorStateList) { - mGradientState.setSolidColors(colorStateList); - final int color; if (colorStateList == null) { - color = Color.TRANSPARENT; + setColor(Color.TRANSPARENT); } else { final int[] stateSet = getState(); - color = colorStateList.getColorForState(stateSet, 0); + final int color = colorStateList.getColorForState(stateSet, 0); + mGradientState.setSolidColors(colorStateList); + mFillPaint.setColor(color); + invalidateSelf(); } - mFillPaint.setColor(color); - invalidateSelf(); } /** @@ -984,8 +1130,9 @@ public class GradientDrawable extends Drawable { } } - if (s.mTint != null && s.mTintMode != null) { - mTintFilter = updateTintFilter(mTintFilter, s.mTint, s.mTintMode); + if (s.mTint != null && s.mBlendMode != null) { + mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, s.mTint, + s.mBlendMode); invalidateSelf = true; } @@ -1058,14 +1205,16 @@ public class GradientDrawable extends Drawable { @Override public void setTintList(@Nullable ColorStateList tint) { mGradientState.mTint = tint; - mTintFilter = updateTintFilter(mTintFilter, tint, mGradientState.mTintMode); + mBlendModeColorFilter = + updateBlendModeFilter(mBlendModeColorFilter, tint, mGradientState.mBlendMode); invalidateSelf(); } @Override - public void setTintMode(@Nullable PorterDuff.Mode tintMode) { - mGradientState.mTintMode = tintMode; - mTintFilter = updateTintFilter(mTintFilter, mGradientState.mTint, tintMode); + public void setTintBlendMode(@NonNull BlendMode blendMode) { + mGradientState.mBlendMode = blendMode; + mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, mGradientState.mTint, + blendMode); invalidateSelf(); } @@ -1121,7 +1270,7 @@ public class GradientDrawable extends Drawable { if (st.mGradient == LINEAR_GRADIENT) { final float level = st.mUseLevel ? getLevel() / 10000.0f : 1.0f; - switch (st.mOrientation) { + switch (st.getOrientation()) { case TOP_BOTTOM: x0 = r.left; y0 = r.top; x1 = x0; y1 = level * r.bottom; @@ -1319,7 +1468,7 @@ public class GradientDrawable extends Drawable { final int tintMode = a.getInt(R.styleable.GradientDrawable_tintMode, -1); if (tintMode != -1) { - state.mTintMode = Drawable.parseTintMode(tintMode, PorterDuff.Mode.SRC_IN); + state.mBlendMode = Drawable.parseBlendMode(tintMode, BlendMode.SRC_IN); } final ColorStateList tint = a.getColorStateList(R.styleable.GradientDrawable_tint); @@ -1358,8 +1507,6 @@ public class GradientDrawable extends Drawable { st.mAttrGradient, R.styleable.GradientDrawableGradient); try { updateGradientDrawableGradient(t.getResources(), a); - } catch (XmlPullParserException e) { - rethrowAsRuntimeException(e); } finally { a.recycle(); } @@ -1547,8 +1694,7 @@ public class GradientDrawable extends Drawable { } } - private void updateGradientDrawableGradient(Resources r, TypedArray a) - throws XmlPullParserException { + private void updateGradientDrawableGradient(Resources r, TypedArray a) { final GradientState st = mGradientState; // Account for any configuration changes. @@ -1566,15 +1712,32 @@ public class GradientDrawable extends Drawable { st.mGradient = a.getInt( R.styleable.GradientDrawableGradient_type, st.mGradient); - // TODO: Update these to be themeable. + final boolean hasGradientColors = st.mGradientColors != null; + final boolean hasGradientCenter = st.hasCenterColor(); + final int prevStart = hasGradientColors ? st.mGradientColors[0] : 0; + final int prevCenter = hasGradientCenter ? st.mGradientColors[1] : 0; + final int prevEnd; + + if (st.hasCenterColor()) { + // if there is a center color, the end color is the last of the 3 values + prevEnd = st.mGradientColors[2]; + } else if (hasGradientColors) { + // if there is not a center color but there are already colors configured, then + // the end color is the 2nd value in the array + prevEnd = st.mGradientColors[1]; + } else { + // otherwise, there isn't a previously configured end color + prevEnd = 0; + } + final int startColor = a.getColor( - R.styleable.GradientDrawableGradient_startColor, 0); + R.styleable.GradientDrawableGradient_startColor, prevStart); final boolean hasCenterColor = a.hasValue( - R.styleable.GradientDrawableGradient_centerColor); + R.styleable.GradientDrawableGradient_centerColor) || hasGradientCenter; final int centerColor = a.getColor( - R.styleable.GradientDrawableGradient_centerColor, 0); + R.styleable.GradientDrawableGradient_centerColor, prevCenter); final int endColor = a.getColor( - R.styleable.GradientDrawableGradient_endColor, 0); + R.styleable.GradientDrawableGradient_endColor, prevEnd); if (hasCenterColor) { st.mGradientColors = new int[3]; @@ -1593,75 +1756,33 @@ public class GradientDrawable extends Drawable { st.mGradientColors[1] = endColor; } - if (st.mGradient == LINEAR_GRADIENT) { - int angle = (int) a.getFloat(R.styleable.GradientDrawableGradient_angle, st.mAngle); - angle %= 360; - - if (angle % 45 != 0) { - throw new XmlPullParserException(a.getPositionDescription() - + "<gradient> tag requires 'angle' attribute to " - + "be a multiple of 45"); - } + int angle = (int) a.getFloat(R.styleable.GradientDrawableGradient_angle, st.mAngle); + st.mAngle = ((angle % 360) + 360) % 360; // offset negative angle measures - st.mAngle = angle; - - switch (angle) { - case 0: - st.mOrientation = Orientation.LEFT_RIGHT; - break; - case 45: - st.mOrientation = Orientation.BL_TR; - break; - case 90: - st.mOrientation = Orientation.BOTTOM_TOP; - break; - case 135: - st.mOrientation = Orientation.BR_TL; - break; - case 180: - st.mOrientation = Orientation.RIGHT_LEFT; - break; - case 225: - st.mOrientation = Orientation.TR_BL; - break; - case 270: - st.mOrientation = Orientation.TOP_BOTTOM; - break; - case 315: - st.mOrientation = Orientation.TL_BR; - break; - } - } else { - final TypedValue tv = a.peekValue(R.styleable.GradientDrawableGradient_gradientRadius); - if (tv != null) { - final float radius; - final @RadiusType int radiusType; - if (tv.type == TypedValue.TYPE_FRACTION) { - radius = tv.getFraction(1.0f, 1.0f); - - final int unit = (tv.data >> TypedValue.COMPLEX_UNIT_SHIFT) - & TypedValue.COMPLEX_UNIT_MASK; - if (unit == TypedValue.COMPLEX_UNIT_FRACTION_PARENT) { - radiusType = RADIUS_TYPE_FRACTION_PARENT; - } else { - radiusType = RADIUS_TYPE_FRACTION; - } - } else if (tv.type == TypedValue.TYPE_DIMENSION) { - radius = tv.getDimension(r.getDisplayMetrics()); - radiusType = RADIUS_TYPE_PIXELS; + final TypedValue tv = a.peekValue(R.styleable.GradientDrawableGradient_gradientRadius); + if (tv != null) { + final float radius; + final @RadiusType int radiusType; + if (tv.type == TypedValue.TYPE_FRACTION) { + radius = tv.getFraction(1.0f, 1.0f); + + final int unit = (tv.data >> TypedValue.COMPLEX_UNIT_SHIFT) + & TypedValue.COMPLEX_UNIT_MASK; + if (unit == TypedValue.COMPLEX_UNIT_FRACTION_PARENT) { + radiusType = RADIUS_TYPE_FRACTION_PARENT; } else { - radius = tv.getFloat(); - radiusType = RADIUS_TYPE_PIXELS; + radiusType = RADIUS_TYPE_FRACTION; } - - st.mGradientRadius = radius; - st.mGradientRadiusType = radiusType; - } else if (st.mGradient == RADIAL_GRADIENT) { - throw new XmlPullParserException( - a.getPositionDescription() - + "<gradient> tag requires 'gradientRadius' " - + "attribute with radial type"); + } else if (tv.type == TypedValue.TYPE_DIMENSION) { + radius = tv.getDimension(r.getDisplayMetrics()); + radiusType = RADIUS_TYPE_PIXELS; + } else { + radius = tv.getFloat(); + radiusType = RADIUS_TYPE_PIXELS; } + + st.mGradientRadius = radius; + st.mGradientRadiusType = radiusType; } } @@ -1698,7 +1819,6 @@ public class GradientDrawable extends Drawable { return mGradientState.mHeight; } - /** @hide */ @Override public Insets getOpticalInsets() { return mGradientState.mOpticalInsets; @@ -1793,27 +1913,46 @@ public class GradientDrawable extends Drawable { final static class GradientState extends ConstantState { public @Config int mChangingConfigurations; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917) public @Shape int mShape = RECTANGLE; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917) public @GradientType int mGradient = LINEAR_GRADIENT; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917) public int mAngle = 0; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917) public Orientation mOrientation; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917) public ColorStateList mSolidColors; public ColorStateList mStrokeColors; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917) public @ColorInt int[] mGradientColors; public @ColorInt int[] mTempColors; // no need to copy public float[] mTempPositions; // no need to copy + @UnsupportedAppUsage public float[] mPositions; + @UnsupportedAppUsage(trackingBug = 124050917) public int mStrokeWidth = -1; // if >= 0 use stroking. + @UnsupportedAppUsage(trackingBug = 124050917) public float mStrokeDashWidth = 0.0f; + @UnsupportedAppUsage(trackingBug = 124050917) public float mStrokeDashGap = 0.0f; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917) public float mRadius = 0.0f; // use this if mRadiusArray is null + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917) public float[] mRadiusArray = null; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917) public Rect mPadding = null; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917) public int mWidth = -1; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917) public int mHeight = -1; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917) public float mInnerRadiusRatio = DEFAULT_INNER_RADIUS_RATIO; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050218) public float mThicknessRatio = DEFAULT_THICKNESS_RATIO; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917) public int mInnerRadius = -1; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050218) public int mThickness = -1; public boolean mDither = false; public Insets mOpticalInsets = Insets.NONE; @@ -1829,7 +1968,7 @@ public class GradientDrawable extends Drawable { boolean mOpaqueOverShape; ColorStateList mTint = null; - PorterDuff.Mode mTintMode = DEFAULT_TINT_MODE; + BlendMode mBlendMode = DEFAULT_BLEND_MODE; int mDensity = DisplayMetrics.DENSITY_DEFAULT; @@ -1842,7 +1981,7 @@ public class GradientDrawable extends Drawable { int[] mAttrPadding; public GradientState(Orientation orientation, int[] gradientColors) { - mOrientation = orientation; + setOrientation(orientation); setGradientColors(gradientColors); } @@ -1887,7 +2026,7 @@ public class GradientDrawable extends Drawable { mOpaqueOverBounds = orig.mOpaqueOverBounds; mOpaqueOverShape = orig.mOpaqueOverShape; mTint = orig.mTint; - mTintMode = orig.mTintMode; + mBlendMode = orig.mBlendMode; mThemeAttrs = orig.mThemeAttrs; mAttrSize = orig.mAttrSize; mAttrGradient = orig.mAttrGradient; @@ -1920,6 +2059,10 @@ public class GradientDrawable extends Drawable { } } + public boolean hasCenterColor() { + return mGradientColors != null && mGradientColors.length == 3; + } + private void applyDensityScaling(int sourceDensity, int targetDensity) { if (mInnerRadius > 0) { mInnerRadius = Drawable.scaleFromDensity( @@ -2041,6 +2184,93 @@ public class GradientDrawable extends Drawable { mCenterY = y; } + public void setOrientation(Orientation orientation) { + // Update the angle here so that subsequent attempts to obtain the orientation + // from the angle overwrite previously configured values during inflation + mAngle = getAngleFromOrientation(orientation); + mOrientation = orientation; + } + + @NonNull + public Orientation getOrientation() { + updateGradientStateOrientation(); + return mOrientation; + } + + /** + * Update the orientation of the gradient based on the given angle only if the type is + * {@link #LINEAR_GRADIENT} + */ + private void updateGradientStateOrientation() { + if (mGradient == LINEAR_GRADIENT) { + int angle = mAngle; + if (angle % 45 != 0) { + throw new IllegalArgumentException("Linear gradient requires 'angle' attribute " + + "to be a multiple of 45"); + } + + Orientation orientation; + switch (angle) { + case 0: + orientation = Orientation.LEFT_RIGHT; + break; + case 45: + orientation = Orientation.BL_TR; + break; + case 90: + orientation = Orientation.BOTTOM_TOP; + break; + case 135: + orientation = Orientation.BR_TL; + break; + case 180: + orientation = Orientation.RIGHT_LEFT; + break; + case 225: + orientation = Orientation.TR_BL; + break; + case 270: + orientation = Orientation.TOP_BOTTOM; + break; + case 315: + orientation = Orientation.TL_BR; + break; + default: + // Should not get here as exception is thrown above if angle is not multiple + // of 45 degrees + orientation = Orientation.LEFT_RIGHT; + break; + } + mOrientation = orientation; + } + } + + private int getAngleFromOrientation(@Nullable Orientation orientation) { + if (orientation != null) { + switch (orientation) { + default: + case LEFT_RIGHT: + return 0; + case BL_TR: + return 45; + case BOTTOM_TOP: + return 90; + case BR_TL: + return 135; + case RIGHT_LEFT: + return 180; + case TR_BL: + return 225; + case TOP_BOTTOM: + return 270; + case TL_BR: + return 315; + } + } else { + return 0; + } + } + public void setGradientColors(@Nullable int[] colors) { mGradientColors = colors; mSolidColors = null; @@ -2170,7 +2400,8 @@ public class GradientDrawable extends Drawable { } } - mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); + mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, state.mTint, + state.mBlendMode); mGradientIsDirty = true; state.computeOpacity(); diff --git a/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java index 361fe0bffbbc..3658f89abae1 100644 --- a/graphics/java/android/graphics/drawable/Icon.java +++ b/graphics/java/android/graphics/drawable/Icon.java @@ -21,6 +21,7 @@ import android.annotation.DrawableRes; import android.annotation.IdRes; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.UnsupportedAppUsage; import android.content.ContentResolver; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -29,9 +30,11 @@ import android.content.res.ColorStateList; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.BlendMode; import android.graphics.PorterDuff; import android.net.Uri; import android.os.AsyncTask; +import android.os.Build; import android.os.Handler; import android.os.Message; import android.os.Parcel; @@ -99,11 +102,12 @@ public final class Icon implements Parcelable { private static final int VERSION_STREAM_SERIALIZER = 1; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private final int mType; private ColorStateList mTintList; - static final PorterDuff.Mode DEFAULT_TINT_MODE = Drawable.DEFAULT_TINT_MODE; // SRC_IN - private PorterDuff.Mode mTintMode = DEFAULT_TINT_MODE; + static final BlendMode DEFAULT_BLEND_MODE = Drawable.DEFAULT_BLEND_MODE; // SRC_IN + private BlendMode mBlendMode = Drawable.DEFAULT_BLEND_MODE; // To avoid adding unnecessary overhead, we have a few basic objects that get repurposed // based on the value of mType. @@ -115,6 +119,7 @@ public final class Icon implements Parcelable { // TYPE_RESOURCE: package name // TYPE_URI: uri string + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private String mString1; // TYPE_RESOURCE: resId @@ -139,6 +144,7 @@ public final class Icon implements Parcelable { * @return The {@link android.graphics.Bitmap} held by this {@link #TYPE_BITMAP} Icon. * @hide */ + @UnsupportedAppUsage public Bitmap getBitmap() { if (mType != TYPE_BITMAP && mType != TYPE_ADAPTIVE_BITMAP) { throw new IllegalStateException("called getBitmap() on " + this); @@ -154,6 +160,7 @@ public final class Icon implements Parcelable { * @return The length of the compressed bitmap byte array held by this {@link #TYPE_DATA} Icon. * @hide */ + @UnsupportedAppUsage public int getDataLength() { if (mType != TYPE_DATA) { throw new IllegalStateException("called getDataLength() on " + this); @@ -168,6 +175,7 @@ public final class Icon implements Parcelable { * valid compressed bitmap data is found. * @hide */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public int getDataOffset() { if (mType != TYPE_DATA) { throw new IllegalStateException("called getDataOffset() on " + this); @@ -182,6 +190,7 @@ public final class Icon implements Parcelable { * bitmap data. * @hide */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public byte[] getDataBytes() { if (mType != TYPE_DATA) { throw new IllegalStateException("called getDataBytes() on " + this); @@ -195,6 +204,7 @@ public final class Icon implements Parcelable { * @return The {@link android.content.res.Resources} for this {@link #TYPE_RESOURCE} Icon. * @hide */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public Resources getResources() { if (mType != TYPE_RESOURCE) { throw new IllegalStateException("called getResources() on " + this); @@ -311,10 +321,10 @@ public final class Icon implements Parcelable { */ public Drawable loadDrawable(Context context) { final Drawable result = loadDrawableInner(context); - if (result != null && (mTintList != null || mTintMode != DEFAULT_TINT_MODE)) { + if (result != null && (mTintList != null || mBlendMode != DEFAULT_BLEND_MODE)) { result.mutate(); result.setTintList(mTintList); - result.setTintMode(mTintMode); + result.setTintBlendMode(mBlendMode); } return result; } @@ -560,6 +570,7 @@ public final class Icon implements Parcelable { * Version of createWithResource that takes Resources. Do not use. * @hide */ + @UnsupportedAppUsage public static Icon createWithResource(Resources res, @DrawableRes int resId) { if (res == null) { throw new IllegalArgumentException("Resource must not be null."); @@ -686,14 +697,26 @@ public final class Icon implements Parcelable { * @param mode a blending mode, as in {@link Drawable#setTintMode(PorterDuff.Mode)}, may be null * @return this same object, for use in chained construction */ - public Icon setTintMode(PorterDuff.Mode mode) { - mTintMode = mode; + public @NonNull Icon setTintMode(@NonNull PorterDuff.Mode mode) { + mBlendMode = BlendMode.fromValue(mode.nativeInt); + return this; + } + + /** + * Store a blending mode to use whenever this Icon is drawn. + * + * @param mode a blending mode, as in {@link Drawable#setTintMode(PorterDuff.Mode)}, may be null + * @return this same object, for use in chained construction + */ + public @NonNull Icon setTintBlendMode(@NonNull BlendMode mode) { + mBlendMode = mode; return this; } /** @hide */ + @UnsupportedAppUsage public boolean hasTint() { - return (mTintList != null) || (mTintMode != DEFAULT_TINT_MODE); + return (mTintList != null) || (mBlendMode != DEFAULT_BLEND_MODE); } /** @@ -746,7 +769,7 @@ public final class Icon implements Parcelable { sep = "|"; } } - if (mTintMode != DEFAULT_TINT_MODE) sb.append(" mode=").append(mTintMode); + if (mBlendMode != DEFAULT_BLEND_MODE) sb.append(" mode=").append(mBlendMode); sb.append(")"); return sb.toString(); } @@ -796,7 +819,7 @@ public final class Icon implements Parcelable { if (in.readInt() == 1) { mTintList = ColorStateList.CREATOR.createFromParcel(in); } - mTintMode = PorterDuff.intToMode(in.readInt()); + mBlendMode = BlendMode.fromValue(in.readInt()); } @Override @@ -826,10 +849,10 @@ public final class Icon implements Parcelable { dest.writeInt(1); mTintList.writeToParcel(dest, flags); } - dest.writeInt(PorterDuff.modeToInt(mTintMode)); + dest.writeInt(BlendMode.toValue(mBlendMode)); } - public static final Parcelable.Creator<Icon> CREATOR + public static final @android.annotation.NonNull Parcelable.Creator<Icon> CREATOR = new Parcelable.Creator<Icon>() { public Icon createFromParcel(Parcel in) { return new Icon(in); diff --git a/graphics/java/android/graphics/drawable/InsetDrawable.java b/graphics/java/android/graphics/drawable/InsetDrawable.java index 443aa4931ee3..bc8a4cbd7e9d 100644 --- a/graphics/java/android/graphics/drawable/InsetDrawable.java +++ b/graphics/java/android/graphics/drawable/InsetDrawable.java @@ -16,13 +16,9 @@ package android.graphics.drawable; -import com.android.internal.R; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UnsupportedAppUsage; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; @@ -35,6 +31,11 @@ import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.TypedValue; +import com.android.internal.R; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + import java.io.IOException; /** @@ -57,6 +58,7 @@ public class InsetDrawable extends DrawableWrapper { private final Rect mTmpRect = new Rect(); private final Rect mTmpInsetRect = new Rect(); + @UnsupportedAppUsage private InsetState mState; /** @@ -240,7 +242,6 @@ public class InsetDrawable extends DrawableWrapper { | mTmpInsetRect.top | mTmpInsetRect.bottom) != 0; } - /** @hide */ @Override public Insets getOpticalInsets() { final Insets contentInsets = super.getOpticalInsets(); diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java index 4725c2c4c0e5..760d554888ee 100644 --- a/graphics/java/android/graphics/drawable/LayerDrawable.java +++ b/graphics/java/android/graphics/drawable/LayerDrawable.java @@ -18,16 +18,17 @@ package android.graphics.drawable; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UnsupportedAppUsage; import android.content.pm.ActivityInfo.Config; import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; +import android.graphics.BlendMode; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Outline; import android.graphics.PixelFormat; -import android.graphics.PorterDuff.Mode; import android.graphics.Rect; import android.util.AttributeSet; import android.util.DisplayMetrics; @@ -93,6 +94,7 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { public static final int INSET_UNDEFINED = Integer.MIN_VALUE; @NonNull + @UnsupportedAppUsage LayerState mLayerState; private int[] mPaddingL; @@ -137,9 +139,12 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { final ChildDrawable[] r = new ChildDrawable[length]; for (int i = 0; i < length; i++) { r[i] = new ChildDrawable(mLayerState.mDensity); - r[i].mDrawable = layers[i]; - layers[i].setCallback(this); - mLayerState.mChildrenChangingConfigurations |= layers[i].getChangingConfigurations(); + Drawable child = layers[i]; + r[i].mDrawable = child; + if (child != null) { + child.setCallback(this); + mLayerState.mChildrenChangingConfigurations |= child.getChangingConfigurations(); + } } mLayerState.mNumChildren = length; mLayerState.mChildren = r; @@ -414,7 +419,8 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { final ChildDrawable[] layers = mLayerState.mChildren; final int N = mLayerState.mNumChildren; for (int i = 0; i < N; i++) { - if (layers[i].mDrawable.isProjected()) { + Drawable childDrawable = layers[i].mDrawable; + if (childDrawable != null && childDrawable.isProjected()) { return true; } } @@ -428,6 +434,7 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { * @param layer The layer to add. * @return The index of the layer. */ + @UnsupportedAppUsage int addLayer(@NonNull ChildDrawable layer) { final LayerState st = mLayerState; final int N = st.mChildren != null ? st.mChildren.length : 0; @@ -1394,13 +1401,13 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { } @Override - public void setTintMode(Mode tintMode) { + public void setTintBlendMode(@NonNull BlendMode blendMode) { final ChildDrawable[] array = mLayerState.mChildren; final int N = mLayerState.mNumChildren; for (int i = 0; i < N; i++) { final Drawable dr = array[i].mDrawable; if (dr != null) { - dr.setTintMode(tintMode); + dr.setTintBlendMode(blendMode); } } } @@ -1739,6 +1746,7 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { /** * Ensures the child padding caches are large enough. */ + @UnsupportedAppUsage void ensurePadding() { final int N = mLayerState.mNumChildren; if (mPaddingL != null && mPaddingL.length >= N) { @@ -1820,6 +1828,7 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { } static class ChildDrawable { + @UnsupportedAppUsage public Drawable mDrawable; public int[] mThemeAttrs; public int mDensity = DisplayMetrics.DENSITY_DEFAULT; @@ -1922,6 +1931,7 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { private int[] mThemeAttrs; int mNumChildren; + @UnsupportedAppUsage ChildDrawable[] mChildren; int mDensity; diff --git a/graphics/java/android/graphics/drawable/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java index 5ff49aba88ae..8561d95ddd88 100644 --- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java +++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java @@ -18,12 +18,15 @@ package android.graphics.drawable; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UnsupportedAppUsage; import android.content.pm.ActivityInfo.Config; import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.graphics.Bitmap; +import android.graphics.BlendMode; +import android.graphics.BlendModeColorFilter; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.ImageDecoder; @@ -32,9 +35,6 @@ import android.graphics.NinePatch; import android.graphics.Outline; import android.graphics.Paint; import android.graphics.PixelFormat; -import android.graphics.PorterDuff; -import android.graphics.PorterDuff.Mode; -import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; import android.graphics.Region; import android.util.AttributeSet; @@ -70,8 +70,9 @@ public class NinePatchDrawable extends Drawable { /** Temporary rect used for density scaling. */ private Rect mTempRect; + @UnsupportedAppUsage private NinePatchState mNinePatchState; - private PorterDuffColorFilter mTintFilter; + private BlendModeColorFilter mBlendModeFilter; private Rect mPadding; private Insets mOpticalInsets = Insets.NONE; private Rect mOutlineInsets; @@ -196,8 +197,8 @@ public class NinePatchDrawable extends Drawable { int restoreToCount = -1; final boolean clearColorFilter; - if (mTintFilter != null && getPaint().getColorFilter() == null) { - mPaint.setColorFilter(mTintFilter); + if (mBlendModeFilter != null && getPaint().getColorFilter() == null) { + mPaint.setColorFilter(mBlendModeFilter); clearColorFilter = true; } else { clearColorFilter = false; @@ -299,9 +300,6 @@ public class NinePatchDrawable extends Drawable { super.getOutline(outline); } - /** - * @hide - */ @Override public Insets getOpticalInsets() { final Insets opticalInsets = mOpticalInsets; @@ -345,14 +343,16 @@ public class NinePatchDrawable extends Drawable { @Override public void setTintList(@Nullable ColorStateList tint) { mNinePatchState.mTint = tint; - mTintFilter = updateTintFilter(mTintFilter, tint, mNinePatchState.mTintMode); + mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, tint, + mNinePatchState.mBlendMode); invalidateSelf(); } @Override - public void setTintMode(@Nullable PorterDuff.Mode tintMode) { - mNinePatchState.mTintMode = tintMode; - mTintFilter = updateTintFilter(mTintFilter, mNinePatchState.mTint, tintMode); + public void setTintBlendMode(@Nullable BlendMode blendMode) { + mNinePatchState.mBlendMode = blendMode; + mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, mNinePatchState.mTint, + blendMode); invalidateSelf(); } @@ -468,7 +468,7 @@ public class NinePatchDrawable extends Drawable { final int tintMode = a.getInt(R.styleable.NinePatchDrawable_tintMode, -1); if (tintMode != -1) { - state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN); + state.mBlendMode = Drawable.parseBlendMode(tintMode, BlendMode.SRC_IN); } final ColorStateList tint = a.getColorStateList(R.styleable.NinePatchDrawable_tint); @@ -567,8 +567,9 @@ public class NinePatchDrawable extends Drawable { @Override protected boolean onStateChange(int[] stateSet) { final NinePatchState state = mNinePatchState; - if (state.mTint != null && state.mTintMode != null) { - mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); + if (state.mTint != null && state.mBlendMode != null) { + mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, state.mTint, + state.mBlendMode); return true; } @@ -591,9 +592,10 @@ public class NinePatchDrawable extends Drawable { @Config int mChangingConfigurations; // Values loaded during inflation. + @UnsupportedAppUsage NinePatch mNinePatch = null; ColorStateList mTint = null; - Mode mTintMode = DEFAULT_TINT_MODE; + BlendMode mBlendMode = DEFAULT_BLEND_MODE; Rect mPadding = null; Insets mOpticalInsets = Insets.NONE; float mBaseAlpha = 1.0f; @@ -628,7 +630,7 @@ public class NinePatchDrawable extends Drawable { mChangingConfigurations = orig.mChangingConfigurations; mNinePatch = orig.mNinePatch; mTint = orig.mTint; - mTintMode = orig.mTintMode; + mBlendMode = orig.mBlendMode; mPadding = orig.mPadding; mOpticalInsets = orig.mOpticalInsets; mBaseAlpha = orig.mBaseAlpha; @@ -751,7 +753,7 @@ public class NinePatchDrawable extends Drawable { } else { mTargetDensity = Drawable.resolveDensity(res, mTargetDensity); } - mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); + mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, state.mTint, state.mBlendMode); computeBitmapSize(); } } diff --git a/graphics/java/android/graphics/drawable/RippleComponent.java b/graphics/java/android/graphics/drawable/RippleComponent.java index 626bcee9454b..c1f8798faaeb 100644 --- a/graphics/java/android/graphics/drawable/RippleComponent.java +++ b/graphics/java/android/graphics/drawable/RippleComponent.java @@ -16,15 +16,8 @@ package android.graphics.drawable; -import android.animation.Animator; -import android.graphics.Canvas; -import android.graphics.Paint; import android.graphics.Rect; import android.util.DisplayMetrics; -import android.view.DisplayListCanvas; -import android.view.RenderNodeAnimator; - -import java.util.ArrayList; /** * Abstract class that handles size & positioning common to the ripple & focus states. diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java index 0da61c29bd8d..1540cc22e295 100644 --- a/graphics/java/android/graphics/drawable/RippleDrawable.java +++ b/graphics/java/android/graphics/drawable/RippleDrawable.java @@ -18,6 +18,7 @@ package android.graphics.drawable; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UnsupportedAppUsage; import android.content.pm.ActivityInfo.Config; import android.content.res.ColorStateList; import android.content.res.Resources; @@ -120,6 +121,7 @@ public class RippleDrawable extends LayerDrawable { private final Rect mDirtyBounds = new Rect(); /** Mirrors mLayerState with some extra information. */ + @UnsupportedAppUsage private RippleState mState; /** The masking layer, e.g. the layer with id R.id.mask. */ @@ -157,6 +159,7 @@ public class RippleDrawable extends LayerDrawable { private Paint mRipplePaint; /** Target density of the display into which ripples are drawn. */ + @UnsupportedAppUsage private int mDensity; /** Whether bounds are being overridden. */ @@ -857,6 +860,7 @@ public class RippleDrawable extends LayerDrawable { mMask.draw(canvas); } + @UnsupportedAppUsage Paint getRipplePaint() { if (mRipplePaint == null) { mRipplePaint = new Paint(); @@ -888,7 +892,10 @@ public class RippleDrawable extends LayerDrawable { // The ripple timing depends on the paint's alpha value, so we need // to push just the alpha channel into the paint and let the filter // handle the full-alpha color. - mMaskColorFilter.setColor(color | 0xFF000000); + int maskColor = color | 0xFF000000; + if (mMaskColorFilter.getColor() != maskColor) { + mMaskColorFilter = new PorterDuffColorFilter(maskColor, mMaskColorFilter.getMode()); + } p.setColor(color & 0xFF000000); p.setColorFilter(mMaskColorFilter); p.setShader(mMaskShader); @@ -942,6 +949,7 @@ public class RippleDrawable extends LayerDrawable { * @param forceSoftware true if RenderThread animations should be disabled, false otherwise * @hide */ + @UnsupportedAppUsage public void setForceSoftware(boolean forceSoftware) { mForceSoftware = forceSoftware; } @@ -972,6 +980,7 @@ public class RippleDrawable extends LayerDrawable { static class RippleState extends LayerState { int[] mTouchThemeAttrs; + @UnsupportedAppUsage ColorStateList mColor = ColorStateList.valueOf(Color.MAGENTA); int mMaxRadius = RADIUS_AUTO; diff --git a/graphics/java/android/graphics/drawable/RippleForeground.java b/graphics/java/android/graphics/drawable/RippleForeground.java index a8dc34af292b..cce9ba31929f 100644 --- a/graphics/java/android/graphics/drawable/RippleForeground.java +++ b/graphics/java/android/graphics/drawable/RippleForeground.java @@ -23,10 +23,10 @@ import android.animation.TimeInterpolator; import android.graphics.Canvas; import android.graphics.CanvasProperty; import android.graphics.Paint; +import android.graphics.RecordingCanvas; import android.graphics.Rect; import android.util.FloatProperty; import android.util.MathUtils; -import android.view.DisplayListCanvas; import android.view.RenderNodeAnimator; import android.view.animation.AnimationUtils; import android.view.animation.LinearInterpolator; @@ -132,7 +132,7 @@ class RippleForeground extends RippleComponent { } } - private void startPending(DisplayListCanvas c) { + private void startPending(RecordingCanvas c) { if (!mPendingHwAnimators.isEmpty()) { for (int i = 0; i < mPendingHwAnimators.size(); i++) { RenderNodeAnimator animator = mPendingHwAnimators.get(i); @@ -164,7 +164,7 @@ class RippleForeground extends RippleComponent { } } - private void drawHardware(DisplayListCanvas c, Paint p) { + private void drawHardware(RecordingCanvas c, Paint p) { startPending(c); pruneHwFinished(); if (mPropPaint != null) { @@ -332,11 +332,11 @@ class RippleForeground extends RippleComponent { * @param p the paint used to draw the ripple */ public void draw(Canvas c, Paint p) { - final boolean hasDisplayListCanvas = !mForceSoftware && c instanceof DisplayListCanvas; + final boolean hasDisplayListCanvas = !mForceSoftware && c instanceof RecordingCanvas; pruneSwFinished(); if (hasDisplayListCanvas) { - final DisplayListCanvas hw = (DisplayListCanvas) c; + final RecordingCanvas hw = (RecordingCanvas) c; drawHardware(hw, p); } else { drawSoftware(c, p); diff --git a/graphics/java/android/graphics/drawable/RotateDrawable.java b/graphics/java/android/graphics/drawable/RotateDrawable.java index c0dfe77cf4f3..db5f082bd853 100644 --- a/graphics/java/android/graphics/drawable/RotateDrawable.java +++ b/graphics/java/android/graphics/drawable/RotateDrawable.java @@ -23,6 +23,7 @@ import org.xmlpull.v1.XmlPullParserException; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UnsupportedAppUsage; import android.graphics.Canvas; import android.graphics.Rect; import android.content.res.Resources; @@ -54,6 +55,7 @@ import java.io.IOException; public class RotateDrawable extends DrawableWrapper { private static final int MAX_LEVEL = 10000; + @UnsupportedAppUsage private RotateState mState; /** diff --git a/graphics/java/android/graphics/drawable/ScaleDrawable.java b/graphics/java/android/graphics/drawable/ScaleDrawable.java index 51e143baeac4..91ed061e511d 100644 --- a/graphics/java/android/graphics/drawable/ScaleDrawable.java +++ b/graphics/java/android/graphics/drawable/ScaleDrawable.java @@ -23,6 +23,7 @@ import org.xmlpull.v1.XmlPullParserException; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UnsupportedAppUsage; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; @@ -66,6 +67,7 @@ public class ScaleDrawable extends DrawableWrapper { private final Rect mTmpRect = new Rect(); + @UnsupportedAppUsage private ScaleState mState; ScaleDrawable() { diff --git a/graphics/java/android/graphics/drawable/ShapeDrawable.java b/graphics/java/android/graphics/drawable/ShapeDrawable.java index 34da928bb6f1..9774b59f98a9 100644 --- a/graphics/java/android/graphics/drawable/ShapeDrawable.java +++ b/graphics/java/android/graphics/drawable/ShapeDrawable.java @@ -24,14 +24,13 @@ import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; +import android.graphics.BlendMode; +import android.graphics.BlendModeColorFilter; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Outline; import android.graphics.Paint; import android.graphics.PixelFormat; -import android.graphics.PorterDuff; -import android.graphics.PorterDuff.Mode; -import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; import android.graphics.Shader; import android.graphics.Xfermode; @@ -75,7 +74,7 @@ import java.io.IOException; */ public class ShapeDrawable extends Drawable { private @NonNull ShapeState mShapeState; - private PorterDuffColorFilter mTintFilter; + private BlendModeColorFilter mBlendModeColorFilter; private boolean mMutated; /** @@ -238,8 +237,8 @@ public class ShapeDrawable extends Drawable { // only draw shape if it may affect output if (paint.getAlpha() != 0 || paint.getXfermode() != null || paint.hasShadowLayer()) { final boolean clearColorFilter; - if (mTintFilter != null && paint.getColorFilter() == null) { - paint.setColorFilter(mTintFilter); + if (mBlendModeColorFilter != null && paint.getColorFilter() == null) { + paint.setColorFilter(mBlendModeColorFilter); clearColorFilter = true; } else { clearColorFilter = false; @@ -292,14 +291,16 @@ public class ShapeDrawable extends Drawable { @Override public void setTintList(ColorStateList tint) { mShapeState.mTint = tint; - mTintFilter = updateTintFilter(mTintFilter, tint, mShapeState.mTintMode); + mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, tint, + mShapeState.mBlendMode); invalidateSelf(); } @Override - public void setTintMode(PorterDuff.Mode tintMode) { - mShapeState.mTintMode = tintMode; - mTintFilter = updateTintFilter(mTintFilter, mShapeState.mTint, tintMode); + public void setTintBlendMode(@NonNull BlendMode blendMode) { + mShapeState.mBlendMode = blendMode; + mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, mShapeState.mTint, + blendMode); invalidateSelf(); } @@ -352,8 +353,9 @@ public class ShapeDrawable extends Drawable { @Override protected boolean onStateChange(int[] stateSet) { final ShapeState state = mShapeState; - if (state.mTint != null && state.mTintMode != null) { - mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); + if (state.mTint != null && state.mBlendMode != null) { + mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, state.mTint, + state.mBlendMode); return true; } return false; @@ -475,7 +477,7 @@ public class ShapeDrawable extends Drawable { final int tintMode = a.getInt(R.styleable.ShapeDrawable_tintMode, -1); if (tintMode != -1) { - state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN); + state.mBlendMode = Drawable.parseBlendMode(tintMode, BlendMode.SRC_IN); } final ColorStateList tint = a.getColorStateList(R.styleable.ShapeDrawable_tint); @@ -540,7 +542,7 @@ public class ShapeDrawable extends Drawable { int[] mThemeAttrs; Shape mShape; ColorStateList mTint; - Mode mTintMode = DEFAULT_TINT_MODE; + BlendMode mBlendMode = DEFAULT_BLEND_MODE; Rect mPadding; int mIntrinsicWidth; int mIntrinsicHeight; @@ -573,7 +575,7 @@ public class ShapeDrawable extends Drawable { } } mTint = orig.mTint; - mTintMode = orig.mTintMode; + mBlendMode = orig.mBlendMode; if (orig.mPadding != null) { mPadding = new Rect(orig.mPadding); } @@ -594,12 +596,12 @@ public class ShapeDrawable extends Drawable { @Override public Drawable newDrawable() { - return new ShapeDrawable(this, null); + return new ShapeDrawable(new ShapeState(this), null); } @Override public Drawable newDrawable(Resources res) { - return new ShapeDrawable(this, res); + return new ShapeDrawable(new ShapeState(this), res); } @Override @@ -625,7 +627,8 @@ public class ShapeDrawable extends Drawable { * after inflating or applying a theme. */ private void updateLocalState() { - mTintFilter = updateTintFilter(mTintFilter, mShapeState.mTint, mShapeState.mTintMode); + mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, mShapeState.mTint, + mShapeState.mBlendMode); } /** diff --git a/graphics/java/android/graphics/drawable/StateListDrawable.java b/graphics/java/android/graphics/drawable/StateListDrawable.java index c98f1608c665..f67188c22609 100644 --- a/graphics/java/android/graphics/drawable/StateListDrawable.java +++ b/graphics/java/android/graphics/drawable/StateListDrawable.java @@ -16,6 +16,15 @@ package android.graphics.drawable; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UnsupportedAppUsage; +import android.content.res.Resources; +import android.content.res.Resources.Theme; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.util.StateSet; + import com.android.internal.R; import org.xmlpull.v1.XmlPullParser; @@ -24,14 +33,6 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.util.Arrays; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.content.res.Resources.Theme; -import android.util.AttributeSet; -import android.util.StateSet; - /** * Lets you assign a number of graphic images to a single Drawable and swap out the visible item by a string * ID value. @@ -63,6 +64,7 @@ public class StateListDrawable extends DrawableContainer { private static final boolean DEBUG = false; + @UnsupportedAppUsage private StateListState mStateListState; private boolean mMutated; @@ -73,9 +75,11 @@ public class StateListDrawable extends DrawableContainer { /** * Add a new image/string ID to the set of images. * - * @param stateSet - An array of resource Ids to associate with the image. + * @param stateSet An array of resource Ids to associate with the image. * Switch to this image by calling setState(). - * @param drawable -The image to show. + * @param drawable The image to show. Note this must be a unique Drawable that is not shared + * between any other View or Drawable otherwise the results are + * undefined and can lead to unexpected rendering behavior */ public void addState(int[] stateSet, Drawable drawable) { if (drawable != null) { @@ -127,6 +131,7 @@ public class StateListDrawable extends DrawableContainer { /** * Updates the constant state from the values in the typed array. */ + @UnsupportedAppUsage private void updateStateFromTypedArray(TypedArray a) { final StateListState state = mStateListState; @@ -204,6 +209,7 @@ public class StateListDrawable extends DrawableContainer { * @param attrs The attribute set. * @return An array of state_ attributes. */ + @UnsupportedAppUsage int[] extractStateSet(AttributeSet attrs) { int j = 0; final int numAttrs = attrs.getAttributeCount(); @@ -235,7 +241,6 @@ public class StateListDrawable extends DrawableContainer { * Gets the number of states contained in this drawable. * * @return The number of states contained in this drawable. - * @hide pending API council * @see #getStateSet(int) * @see #getStateDrawable(int) */ @@ -248,11 +253,10 @@ public class StateListDrawable extends DrawableContainer { * * @param index The index of the state set. * @return The state set at the index. - * @hide pending API council * @see #getStateCount() * @see #getStateDrawable(int) */ - public int[] getStateSet(int index) { + public @NonNull int[] getStateSet(int index) { return mStateListState.mStateSets[index]; } @@ -261,11 +265,10 @@ public class StateListDrawable extends DrawableContainer { * * @param index The index of the drawable. * @return The drawable at the index. - * @hide pending API council * @see #getStateCount() * @see #getStateSet(int) */ - public Drawable getStateDrawable(int index) { + public @Nullable Drawable getStateDrawable(int index) { return mStateListState.getChild(index); } @@ -274,11 +277,10 @@ public class StateListDrawable extends DrawableContainer { * * @param stateSet the state set to look up * @return the index of the provided state set, or -1 if not found - * @hide pending API council * @see #getStateDrawable(int) * @see #getStateSet(int) */ - public int getStateDrawableIndex(int[] stateSet) { + public int findStateDrawableIndex(@NonNull int[] stateSet) { return mStateListState.indexOfStateSet(stateSet); } @@ -331,6 +333,7 @@ public class StateListDrawable extends DrawableContainer { mStateSets = stateSets; } + @UnsupportedAppUsage int addStateSet(int[] stateSet, Drawable drawable) { final int pos = addChild(drawable); mStateSets[pos] = stateSet; diff --git a/graphics/java/android/graphics/drawable/TransitionDrawable.java b/graphics/java/android/graphics/drawable/TransitionDrawable.java index 3dfd68018a6b..276f3662189b 100644 --- a/graphics/java/android/graphics/drawable/TransitionDrawable.java +++ b/graphics/java/android/graphics/drawable/TransitionDrawable.java @@ -16,6 +16,7 @@ package android.graphics.drawable; +import android.annotation.UnsupportedAppUsage; import android.content.pm.ActivityInfo.Config; import android.content.res.Resources; import android.graphics.Canvas; @@ -65,10 +66,13 @@ public class TransitionDrawable extends LayerDrawable implements Drawable.Callba private boolean mReverse; private long mStartTimeMillis; private int mFrom; + @UnsupportedAppUsage private int mTo; private int mDuration; private int mOriginalDuration; + @UnsupportedAppUsage private int mAlpha = 0; + @UnsupportedAppUsage private boolean mCrossFade; /** diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java index c71585f32155..aa19b2a0e94c 100644 --- a/graphics/java/android/graphics/drawable/VectorDrawable.java +++ b/graphics/java/android/graphics/drawable/VectorDrawable.java @@ -16,6 +16,7 @@ package android.graphics.drawable; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UnsupportedAppUsage; import android.content.pm.ActivityInfo.Config; import android.content.res.ColorStateList; import android.content.res.ComplexColor; @@ -23,11 +24,13 @@ import android.content.res.GradientColor; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; +import android.graphics.BlendMode; +import android.graphics.BlendModeColorFilter; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Insets; import android.graphics.PixelFormat; -import android.graphics.PorterDuff.Mode; +import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; import android.graphics.Shader; @@ -46,6 +49,9 @@ import android.util.Xml; import com.android.internal.R; import com.android.internal.util.VirtualRefBasePtr; +import dalvik.annotation.optimization.FastNative; +import dalvik.system.VMRuntime; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -56,9 +62,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Stack; -import dalvik.annotation.optimization.FastNative; -import dalvik.system.VMRuntime; - /** * This lets you create a drawable based on an XML vector graphic. * <p/> @@ -321,7 +324,10 @@ public class VectorDrawable extends Drawable { private VectorDrawableState mVectorState; + @UnsupportedAppUsage private PorterDuffColorFilter mTintFilter; + + private BlendModeColorFilter mBlendModeColorFilter; private ColorFilter mColorFilter; private boolean mMutated; @@ -369,7 +375,7 @@ public class VectorDrawable extends Drawable { mDpiScaledDirty = true; } - mTintFilter = updateTintFilter(mTintFilter, mVectorState.mTint, mVectorState.mTintMode); + updateColorFilters(mVectorState.mBlendMode, mVectorState.mTint); } @Override @@ -389,6 +395,7 @@ public class VectorDrawable extends Drawable { mMutated = false; } + @UnsupportedAppUsage Object getTargetByName(String name) { return mVectorState.mVGTargetsMap.get(name); } @@ -410,7 +417,8 @@ public class VectorDrawable extends Drawable { } // Color filters always override tint filters. - final ColorFilter colorFilter = (mColorFilter == null ? mTintFilter : mColorFilter); + final ColorFilter colorFilter = (mColorFilter == null ? mBlendModeColorFilter : + mColorFilter); final long colorFilterNativeInstance = colorFilter == null ? 0 : colorFilter.getNativeInstance(); boolean canReuseCache = mVectorState.canReuseCache(); @@ -472,17 +480,19 @@ public class VectorDrawable extends Drawable { final VectorDrawableState state = mVectorState; if (state.mTint != tint) { state.mTint = tint; - mTintFilter = updateTintFilter(mTintFilter, tint, state.mTintMode); + + updateColorFilters(mVectorState.mBlendMode, tint); invalidateSelf(); } } @Override - public void setTintMode(Mode tintMode) { + public void setTintBlendMode(@NonNull BlendMode blendMode) { final VectorDrawableState state = mVectorState; - if (state.mTintMode != tintMode) { - state.mTintMode = tintMode; - mTintFilter = updateTintFilter(mTintFilter, state.mTint, tintMode); + if (state.mBlendMode != blendMode) { + state.mBlendMode = blendMode; + + updateColorFilters(state.mBlendMode, state.mTint); invalidateSelf(); } } @@ -512,14 +522,22 @@ public class VectorDrawable extends Drawable { changed = true; state.mCacheDirty = true; } - if (state.mTint != null && state.mTintMode != null) { - mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); + if (state.mTint != null && state.mBlendMode != null) { + BlendMode blendMode = state.mBlendMode; + ColorStateList tint = state.mTint; + updateColorFilters(blendMode, tint); changed = true; } return changed; } + private void updateColorFilters(@Nullable BlendMode blendMode, ColorStateList tint) { + PorterDuff.Mode mode = BlendMode.blendModeToPorterDuffMode(blendMode); + mTintFilter = updateTintFilter(mTintFilter, tint, mode); + mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, tint, blendMode); + } + @Override public int getOpacity() { // We can't tell whether the drawable is fully opaque unless we examine all the pixels, @@ -543,7 +561,6 @@ public class VectorDrawable extends Drawable { return mDpiScaledHeight; } - /** @hide */ @Override public Insets getOpticalInsets() { if (mDpiScaledDirty) { @@ -735,7 +752,7 @@ public class VectorDrawable extends Drawable { final int tintMode = a.getInt(R.styleable.VectorDrawable_tintMode, -1); if (tintMode != -1) { - state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN); + state.mBlendMode = Drawable.parseBlendMode(tintMode, BlendMode.SRC_IN); } final ColorStateList tint = a.getColorStateList(R.styleable.VectorDrawable_tint); @@ -868,6 +885,7 @@ public class VectorDrawable extends Drawable { return super.getChangingConfigurations() | mVectorState.getChangingConfigurations(); } + @UnsupportedAppUsage void setAllowCaching(boolean allowCaching) { nSetAllowCaching(mVectorState.getNativeRenderer(), allowCaching); } @@ -908,7 +926,7 @@ public class VectorDrawable extends Drawable { int[] mThemeAttrs; @Config int mChangingConfigurations; ColorStateList mTint = null; - Mode mTintMode = DEFAULT_TINT_MODE; + BlendMode mBlendMode = DEFAULT_BLEND_MODE; boolean mAutoMirrored; int mBaseWidth = 0; @@ -926,7 +944,7 @@ public class VectorDrawable extends Drawable { // Fields for cache int[] mCachedThemeAttrs; ColorStateList mCachedTint; - Mode mCachedTintMode; + BlendMode mCachedBlendMode; boolean mCachedAutoMirrored; boolean mCacheDirty; @@ -967,7 +985,7 @@ public class VectorDrawable extends Drawable { mThemeAttrs = copy.mThemeAttrs; mChangingConfigurations = copy.mChangingConfigurations; mTint = copy.mTint; - mTintMode = copy.mTintMode; + mBlendMode = copy.mBlendMode; mAutoMirrored = copy.mAutoMirrored; mRootGroup = new VGroup(copy.mRootGroup, mVGTargetsMap); createNativeTreeFromCopy(copy, mRootGroup); @@ -1023,7 +1041,7 @@ public class VectorDrawable extends Drawable { if (!mCacheDirty && mCachedThemeAttrs == mThemeAttrs && mCachedTint == mTint - && mCachedTintMode == mTintMode + && mCachedBlendMode == mBlendMode && mCachedAutoMirrored == mAutoMirrored) { return true; } @@ -1036,7 +1054,7 @@ public class VectorDrawable extends Drawable { // likely hit cache miss more, but practically not much difference. mCachedThemeAttrs = mThemeAttrs; mCachedTint = mTint; - mCachedTintMode = mTintMode; + mCachedBlendMode = mBlendMode; mCachedAutoMirrored = mAutoMirrored; mCacheDirty = false; } @@ -1499,6 +1517,7 @@ public class VectorDrawable extends Drawable { } @SuppressWarnings("unused") + @UnsupportedAppUsage public void setRotation(float rotation) { if (isTreeValid()) { nSetRotation(mNativePtr, rotation); @@ -1511,6 +1530,7 @@ public class VectorDrawable extends Drawable { } @SuppressWarnings("unused") + @UnsupportedAppUsage public void setPivotX(float pivotX) { if (isTreeValid()) { nSetPivotX(mNativePtr, pivotX); @@ -1523,6 +1543,7 @@ public class VectorDrawable extends Drawable { } @SuppressWarnings("unused") + @UnsupportedAppUsage public void setPivotY(float pivotY) { if (isTreeValid()) { nSetPivotY(mNativePtr, pivotY); @@ -1559,6 +1580,7 @@ public class VectorDrawable extends Drawable { } @SuppressWarnings("unused") + @UnsupportedAppUsage public void setTranslateX(float translateX) { if (isTreeValid()) { nSetTranslateX(mNativePtr, translateX); @@ -1571,6 +1593,7 @@ public class VectorDrawable extends Drawable { } @SuppressWarnings("unused") + @UnsupportedAppUsage public void setTranslateY(float translateY) { if (isTreeValid()) { nSetTranslateY(mNativePtr, translateY); @@ -2025,7 +2048,7 @@ public class VectorDrawable extends Drawable { if (fillColors instanceof GradientColor) { mFillColors = fillColors; fillGradient = ((GradientColor) fillColors).getShader(); - } else if (fillColors.isStateful()) { + } else if (fillColors.isStateful() || fillColors.canApplyTheme()) { mFillColors = fillColors; } else { mFillColors = null; @@ -2041,7 +2064,7 @@ public class VectorDrawable extends Drawable { if (strokeColors instanceof GradientColor) { mStrokeColors = strokeColors; strokeGradient = ((GradientColor) strokeColors).getShader(); - } else if (strokeColors.isStateful()) { + } else if (strokeColors.isStateful() || strokeColors.canApplyTheme()) { mStrokeColors = strokeColors; } else { mStrokeColors = null; diff --git a/graphics/java/android/graphics/drawable/shapes/ArcShape.java b/graphics/java/android/graphics/drawable/shapes/ArcShape.java index 85ba0a9e9f6f..90d07bce2617 100644 --- a/graphics/java/android/graphics/drawable/shapes/ArcShape.java +++ b/graphics/java/android/graphics/drawable/shapes/ArcShape.java @@ -20,6 +20,8 @@ import android.graphics.Canvas; import android.graphics.Outline; import android.graphics.Paint; +import java.util.Objects; + /** * Creates an arc shape. The arc shape starts at a specified angle and sweeps * clockwise, drawing slices of pie. @@ -74,5 +76,26 @@ public class ArcShape extends RectShape { public ArcShape clone() throws CloneNotSupportedException { return (ArcShape) super.clone(); } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + ArcShape arcShape = (ArcShape) o; + return Float.compare(arcShape.mStartAngle, mStartAngle) == 0 + && Float.compare(arcShape.mSweepAngle, mSweepAngle) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), mStartAngle, mSweepAngle); + } } diff --git a/graphics/java/android/graphics/drawable/shapes/PathShape.java b/graphics/java/android/graphics/drawable/shapes/PathShape.java index ce5552b7a1bb..393fdee87bb8 100644 --- a/graphics/java/android/graphics/drawable/shapes/PathShape.java +++ b/graphics/java/android/graphics/drawable/shapes/PathShape.java @@ -21,6 +21,8 @@ import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; +import java.util.Objects; + /** * Creates geometric paths, utilizing the {@link android.graphics.Path} class. * <p> @@ -74,5 +76,30 @@ public class PathShape extends Shape { shape.mPath = new Path(mPath); return shape; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + PathShape pathShape = (PathShape) o; + return Float.compare(pathShape.mStdWidth, mStdWidth) == 0 + && Float.compare(pathShape.mStdHeight, mStdHeight) == 0 + && Float.compare(pathShape.mScaleX, mScaleX) == 0 + && Float.compare(pathShape.mScaleY, mScaleY) == 0 + // Path does not have equals implementation but incase it gains one, use it here + && Objects.equals(mPath, pathShape.mPath); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), mStdWidth, mStdHeight, mPath, mScaleX, mScaleY); + } } diff --git a/graphics/java/android/graphics/drawable/shapes/RectShape.java b/graphics/java/android/graphics/drawable/shapes/RectShape.java index e339a212a794..1bbd1d7236d9 100644 --- a/graphics/java/android/graphics/drawable/shapes/RectShape.java +++ b/graphics/java/android/graphics/drawable/shapes/RectShape.java @@ -21,6 +21,8 @@ import android.graphics.Outline; import android.graphics.Paint; import android.graphics.RectF; +import java.util.Objects; + /** * Defines a rectangle shape. * <p> @@ -63,4 +65,24 @@ public class RectShape extends Shape { shape.mRect = new RectF(mRect); return shape; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + RectShape rectShape = (RectShape) o; + return Objects.equals(mRect, rectShape.mRect); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), mRect); + } } diff --git a/graphics/java/android/graphics/drawable/shapes/RoundRectShape.java b/graphics/java/android/graphics/drawable/shapes/RoundRectShape.java index f5cbb24a53ef..475e0bb70f2b 100644 --- a/graphics/java/android/graphics/drawable/shapes/RoundRectShape.java +++ b/graphics/java/android/graphics/drawable/shapes/RoundRectShape.java @@ -23,6 +23,9 @@ import android.graphics.Paint; import android.graphics.Path; import android.graphics.RectF; +import java.util.Arrays; +import java.util.Objects; + /** * Creates a rounded-corner rectangle. Optionally, an inset (rounded) rectangle * can be included (to make a sort of "O" shape). @@ -137,4 +140,31 @@ public class RoundRectShape extends RectShape { shape.mPath = new Path(mPath); return shape; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + RoundRectShape that = (RoundRectShape) o; + return Arrays.equals(mOuterRadii, that.mOuterRadii) + && Objects.equals(mInset, that.mInset) + && Arrays.equals(mInnerRadii, that.mInnerRadii) + && Objects.equals(mInnerRect, that.mInnerRect) + && Objects.equals(mPath, that.mPath); + } + + @Override + public int hashCode() { + int result = Objects.hash(super.hashCode(), mInset, mInnerRect, mPath); + result = 31 * result + Arrays.hashCode(mOuterRadii); + result = 31 * result + Arrays.hashCode(mInnerRadii); + return result; + } } diff --git a/graphics/java/android/graphics/drawable/shapes/Shape.java b/graphics/java/android/graphics/drawable/shapes/Shape.java index 30b28f3c2242..3b044ec631a2 100644 --- a/graphics/java/android/graphics/drawable/shapes/Shape.java +++ b/graphics/java/android/graphics/drawable/shapes/Shape.java @@ -21,6 +21,8 @@ import android.graphics.Canvas; import android.graphics.Outline; import android.graphics.Paint; +import java.util.Objects; + /** * Defines a generic graphical "shape." * <p> @@ -115,4 +117,22 @@ public abstract class Shape implements Cloneable { public Shape clone() throws CloneNotSupportedException { return (Shape) super.clone(); } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Shape shape = (Shape) o; + return Float.compare(shape.mWidth, mWidth) == 0 + && Float.compare(shape.mHeight, mHeight) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(mWidth, mHeight); + } } diff --git a/graphics/java/android/graphics/fonts/Font.java b/graphics/java/android/graphics/fonts/Font.java new file mode 100644 index 000000000000..552088f7c478 --- /dev/null +++ b/graphics/java/android/graphics/fonts/Font.java @@ -0,0 +1,541 @@ +/* + * Copyright (C) 2018 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.graphics.fonts; + +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.res.AssetManager; +import android.content.res.Resources; +import android.os.LocaleList; +import android.os.ParcelFileDescriptor; +import android.util.TypedValue; + +import com.android.internal.util.Preconditions; + +import dalvik.annotation.optimization.CriticalNative; + +import libcore.util.NativeAllocationRegistry; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.util.Arrays; +import java.util.Objects; + +/** + * A font class can be used for creating FontFamily. + */ +public final class Font { + private static final String TAG = "Font"; + + private static final int NOT_SPECIFIED = -1; + private static final int STYLE_ITALIC = 1; + private static final int STYLE_NORMAL = 0; + + /** + * A builder class for creating new Font. + */ + public static final class Builder { + private static final NativeAllocationRegistry sAssetByteBufferRegistry = + NativeAllocationRegistry.createMalloced(ByteBuffer.class.getClassLoader(), + nGetReleaseNativeAssetFunc()); + + private static final NativeAllocationRegistry sFontRegistry = + NativeAllocationRegistry.createMalloced(Font.class.getClassLoader(), + nGetReleaseNativeFont()); + + private @Nullable ByteBuffer mBuffer; + private @Nullable File mFile; + private @NonNull String mLocaleList = ""; + private @IntRange(from = -1, to = 1000) int mWeight = NOT_SPECIFIED; + private @IntRange(from = -1, to = 1) int mItalic = NOT_SPECIFIED; + private @IntRange(from = 0) int mTtcIndex = 0; + private @Nullable FontVariationAxis[] mAxes = null; + private @Nullable IOException mException; + + /** + * Constructs a builder with a byte buffer. + * + * Note that only direct buffer can be used as the source of font data. + * + * @see ByteBuffer#allocateDirect(int) + * @param buffer a byte buffer of a font data + */ + public Builder(@NonNull ByteBuffer buffer) { + Preconditions.checkNotNull(buffer, "buffer can not be null"); + if (!buffer.isDirect()) { + throw new IllegalArgumentException( + "Only direct buffer can be used as the source of font data."); + } + mBuffer = buffer; + } + + /** + * Construct a builder with a byte buffer and file path. + * + * This method is intended to be called only from SystemFonts. + * @hide + */ + public Builder(@NonNull ByteBuffer buffer, @NonNull File path, + @NonNull String localeList) { + this(buffer); + mFile = path; + mLocaleList = localeList; + } + + /** + * Constructs a builder with a file path. + * + * @param path a file path to the font file + */ + public Builder(@NonNull File path) { + Preconditions.checkNotNull(path, "path can not be null"); + try (FileInputStream fis = new FileInputStream(path)) { + final FileChannel fc = fis.getChannel(); + mBuffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()); + } catch (IOException e) { + mException = e; + } + mFile = path; + } + + /** + * Constructs a builder with a file descriptor. + * + * @param fd a file descriptor + */ + public Builder(@NonNull ParcelFileDescriptor fd) { + this(fd, 0, -1); + } + + /** + * Constructs a builder with a file descriptor. + * + * @param fd a file descriptor + * @param offset an offset to of the font data in the file + * @param size a size of the font data. If -1 is passed, use until end of the file. + */ + public Builder(@NonNull ParcelFileDescriptor fd, @IntRange(from = 0) long offset, + @IntRange(from = -1) long size) { + try (FileInputStream fis = new FileInputStream(fd.getFileDescriptor())) { + final FileChannel fc = fis.getChannel(); + size = (size == -1) ? fc.size() - offset : size; + mBuffer = fc.map(FileChannel.MapMode.READ_ONLY, offset, size); + } catch (IOException e) { + mException = e; + } + } + + /** + * Constructs a builder from an asset manager and a file path in an asset directory. + * + * @param am the application's asset manager + * @param path the file name of the font data in the asset directory + */ + public Builder(@NonNull AssetManager am, @NonNull String path) { + this(am, path, true /* is asset */, 0 /* cookie */); + } + + /** + * Constructs a builder from an asset manager and a file path in an asset directory. + * + * @param am the application's asset manager + * @param path the file name of the font data in the asset directory + * @param isAsset true if the undelying data is in asset + * @param cookie set asset cookie + * @hide + */ + public Builder(@NonNull AssetManager am, @NonNull String path, boolean isAsset, + int cookie) { + final long nativeAsset = nGetNativeAsset(am, path, isAsset, cookie); + if (nativeAsset == 0) { + mException = new FileNotFoundException("Unable to open " + path); + return; + } + final ByteBuffer b = nGetAssetBuffer(nativeAsset); + sAssetByteBufferRegistry.registerNativeAllocation(b, nativeAsset); + if (b == null) { + mException = new FileNotFoundException(path + " not found"); + return; + } + mBuffer = b; + } + + /** + * Constructs a builder from resources. + * + * Resource ID must points the font file. XML font can not be used here. + * + * @param res the resource of this application. + * @param resId the resource ID of font file. + */ + public Builder(@NonNull Resources res, int resId) { + final TypedValue value = new TypedValue(); + res.getValue(resId, value, true); + if (value.string == null) { + mException = new FileNotFoundException(resId + " not found"); + return; + } + final String str = value.string.toString(); + if (str.toLowerCase().endsWith(".xml")) { + mException = new FileNotFoundException(resId + " must be font file."); + return; + } + final long nativeAsset = nGetNativeAsset(res.getAssets(), str, false /* is asset */, + value.assetCookie); + if (nativeAsset == 0) { + mException = new FileNotFoundException("Unable to open " + str); + return; + } + final ByteBuffer b = nGetAssetBuffer(nativeAsset); + sAssetByteBufferRegistry.registerNativeAllocation(b, nativeAsset); + if (b == null) { + mException = new FileNotFoundException(str + " not found"); + return; + } + mBuffer = b; + } + + /** + * Sets weight of the font. + * + * Tells the system the weight of the given font. If this function is not called, the system + * will resolve the weight value by reading font tables. + * + * Here are pairs of the common names and their values. + * <p> + * <table> + * <thead> + * <tr> + * <th align="center">Value</th> + * <th align="center">Name</th> + * <th align="center">Android Definition</th> + * </tr> + * </thead> + * <tbody> + * <tr> + * <td align="center">100</td> + * <td align="center">Thin</td> + * <td align="center">{@link FontStyle#FONT_WEIGHT_THIN}</td> + * </tr> + * <tr> + * <td align="center">200</td> + * <td align="center">Extra Light (Ultra Light)</td> + * <td align="center">{@link FontStyle#FONT_WEIGHT_EXTRA_LIGHT}</td> + * </tr> + * <tr> + * <td align="center">300</td> + * <td align="center">Light</td> + * <td align="center">{@link FontStyle#FONT_WEIGHT_LIGHT}</td> + * </tr> + * <tr> + * <td align="center">400</td> + * <td align="center">Normal (Regular)</td> + * <td align="center">{@link FontStyle#FONT_WEIGHT_NORMAL}</td> + * </tr> + * <tr> + * <td align="center">500</td> + * <td align="center">Medium</td> + * <td align="center">{@link FontStyle#FONT_WEIGHT_MEDIUM}</td> + * </tr> + * <tr> + * <td align="center">600</td> + * <td align="center">Semi Bold (Demi Bold)</td> + * <td align="center">{@link FontStyle#FONT_WEIGHT_SEMI_BOLD}</td> + * </tr> + * <tr> + * <td align="center">700</td> + * <td align="center">Bold</td> + * <td align="center">{@link FontStyle#FONT_WEIGHT_BOLD}</td> + * </tr> + * <tr> + * <td align="center">800</td> + * <td align="center">Extra Bold (Ultra Bold)</td> + * <td align="center">{@link FontStyle#FONT_WEIGHT_EXTRA_BOLD}</td> + * </tr> + * <tr> + * <td align="center">900</td> + * <td align="center">Black (Heavy)</td> + * <td align="center">{@link FontStyle#FONT_WEIGHT_BLACK}</td> + * </tr> + * </tbody> + * </p> + * + * @see FontStyle#FONT_WEIGHT_THIN + * @see FontStyle#FONT_WEIGHT_EXTRA_LIGHT + * @see FontStyle#FONT_WEIGHT_LIGHT + * @see FontStyle#FONT_WEIGHT_NORMAL + * @see FontStyle#FONT_WEIGHT_MEDIUM + * @see FontStyle#FONT_WEIGHT_SEMI_BOLD + * @see FontStyle#FONT_WEIGHT_BOLD + * @see FontStyle#FONT_WEIGHT_EXTRA_BOLD + * @see FontStyle#FONT_WEIGHT_BLACK + * @param weight a weight value + * @return this builder + */ + public @NonNull Builder setWeight( + @IntRange(from = FontStyle.FONT_WEIGHT_MIN, to = FontStyle.FONT_WEIGHT_MAX) + int weight) { + Preconditions.checkArgument( + FontStyle.FONT_WEIGHT_MIN <= weight && weight <= FontStyle.FONT_WEIGHT_MAX); + mWeight = weight; + return this; + } + + /** + * Sets italic information of the font. + * + * Tells the system the style of the given font. If this function is not called, the system + * will resolve the style by reading font tables. + * + * For example, if you want to use italic font as upright font, call {@code + * setSlant(FontStyle.FONT_SLANT_UPRIGHT)} explicitly. + * + * @return this builder + */ + public @NonNull Builder setSlant(@FontStyle.FontSlant int slant) { + mItalic = slant == FontStyle.FONT_SLANT_UPRIGHT ? STYLE_NORMAL : STYLE_ITALIC; + return this; + } + + /** + * Sets an index of the font collection. See {@link android.R.attr#ttcIndex}. + * + * @param ttcIndex An index of the font collection. If the font source is not font + * collection, do not call this method or specify 0. + * @return this builder + */ + public @NonNull Builder setTtcIndex(@IntRange(from = 0) int ttcIndex) { + mTtcIndex = ttcIndex; + return this; + } + + /** + * Sets the font variation settings. + * + * @param variationSettings see {@link FontVariationAxis#fromFontVariationSettings(String)} + * @return this builder + * @throws IllegalArgumentException If given string is not a valid font variation settings + * format. + */ + public @NonNull Builder setFontVariationSettings(@Nullable String variationSettings) { + mAxes = FontVariationAxis.fromFontVariationSettings(variationSettings); + return this; + } + + /** + * Sets the font variation settings. + * + * @param axes an array of font variation axis tag-value pairs + * @return this builder + */ + public @NonNull Builder setFontVariationSettings(@Nullable FontVariationAxis[] axes) { + mAxes = axes == null ? null : axes.clone(); + return this; + } + + /** + * Creates the font based on the configured values. + * @return the Font object + */ + public @NonNull Font build() throws IOException { + if (mException != null) { + throw new IOException("Failed to read font contents", mException); + } + if (mWeight == NOT_SPECIFIED || mItalic == NOT_SPECIFIED) { + final int packed = FontFileUtil.analyzeStyle(mBuffer, mTtcIndex, mAxes); + if (FontFileUtil.isSuccess(packed)) { + if (mWeight == NOT_SPECIFIED) { + mWeight = FontFileUtil.unpackWeight(packed); + } + if (mItalic == NOT_SPECIFIED) { + mItalic = FontFileUtil.unpackItalic(packed) ? STYLE_ITALIC : STYLE_NORMAL; + } + } else { + mWeight = 400; + mItalic = STYLE_NORMAL; + } + } + mWeight = Math.max(FontStyle.FONT_WEIGHT_MIN, + Math.min(FontStyle.FONT_WEIGHT_MAX, mWeight)); + final boolean italic = (mItalic == STYLE_ITALIC); + final int slant = (mItalic == STYLE_ITALIC) + ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT; + final long builderPtr = nInitBuilder(); + if (mAxes != null) { + for (FontVariationAxis axis : mAxes) { + nAddAxis(builderPtr, axis.getOpenTypeTagValue(), axis.getStyleValue()); + } + } + final ByteBuffer readonlyBuffer = mBuffer.asReadOnlyBuffer(); + final String filePath = mFile == null ? "" : mFile.getAbsolutePath(); + final long ptr = nBuild(builderPtr, readonlyBuffer, filePath, mWeight, italic, + mTtcIndex); + final Font font = new Font(ptr, readonlyBuffer, mFile, + new FontStyle(mWeight, slant), mTtcIndex, mAxes, mLocaleList); + sFontRegistry.registerNativeAllocation(font, ptr); + return font; + } + + /** + * Native methods for accessing underlying buffer in Asset + */ + private static native long nGetNativeAsset( + @NonNull AssetManager am, @NonNull String path, boolean isAsset, int cookie); + private static native ByteBuffer nGetAssetBuffer(long nativeAsset); + @CriticalNative + private static native long nGetReleaseNativeAssetFunc(); + + /** + * Native methods for creating Font + */ + private static native long nInitBuilder(); + @CriticalNative + private static native void nAddAxis(long builderPtr, int tag, float value); + private static native long nBuild( + long builderPtr, @NonNull ByteBuffer buffer, @NonNull String filePath, int weight, + boolean italic, int ttcIndex); + @CriticalNative + private static native long nGetReleaseNativeFont(); + } + + private final long mNativePtr; // address of the shared ptr of minikin::Font + private final @NonNull ByteBuffer mBuffer; + private final @Nullable File mFile; + private final FontStyle mFontStyle; + private final @IntRange(from = 0) int mTtcIndex; + private final @Nullable FontVariationAxis[] mAxes; + private final @NonNull String mLocaleList; + + /** + * Use Builder instead + */ + private Font(long nativePtr, @NonNull ByteBuffer buffer, @Nullable File file, + @NonNull FontStyle fontStyle, @IntRange(from = 0) int ttcIndex, + @Nullable FontVariationAxis[] axes, @NonNull String localeList) { + mBuffer = buffer; + mFile = file; + mFontStyle = fontStyle; + mNativePtr = nativePtr; + mTtcIndex = ttcIndex; + mAxes = axes; + mLocaleList = localeList; + } + + /** + * Returns a font file buffer. + * + * @return a font buffer + */ + public @NonNull ByteBuffer getBuffer() { + return mBuffer; + } + + /** + * Returns a file path of this font. + * + * This returns null if this font is not created from regular file. + * + * @return a file path of the font + */ + public @Nullable File getFile() { + return mFile; + } + + /** + * Get a style associated with this font. + * + * @see Builder#setWeight(int) + * @see Builder#setSlant(int) + * @return a font style + */ + public @NonNull FontStyle getStyle() { + return mFontStyle; + } + + /** + * Get a TTC index value associated with this font. + * + * If TTF/OTF file is provided, this value is always 0. + * + * @see Builder#setTtcIndex(int) + * @return a TTC index value + */ + public @IntRange(from = 0) int getTtcIndex() { + return mTtcIndex; + } + + /** + * Get a font variation settings associated with this font + * + * @see Builder#setFontVariationSettings(String) + * @see Builder#setFontVariationSettings(FontVariationAxis[]) + * @return font variation settings + */ + public @Nullable FontVariationAxis[] getAxes() { + return mAxes == null ? null : mAxes.clone(); + } + + /** + * Get a locale list of this font. + * + * This is always empty if this font is not a system font. + * @return a locale list + */ + public @NonNull LocaleList getLocaleList() { + return LocaleList.forLanguageTags(mLocaleList); + } + + /** @hide */ + public long getNativePtr() { + return mNativePtr; + } + + @Override + public boolean equals(@Nullable Object o) { + if (o == this) { + return true; + } + if (o == null || !(o instanceof Font)) { + return false; + } + Font f = (Font) o; + return mFontStyle.equals(f.mFontStyle) && f.mTtcIndex == mTtcIndex + && Arrays.equals(f.mAxes, mAxes) && f.mBuffer.equals(mBuffer); + } + + @Override + public int hashCode() { + return Objects.hash(mFontStyle, mTtcIndex, Arrays.hashCode(mAxes), mBuffer); + } + + @Override + public String toString() { + return "Font {" + + "path=" + mFile + + ", style=" + mFontStyle + + ", ttcIndex=" + mTtcIndex + + ", axes=" + FontVariationAxis.toFontVariationSettings(mAxes) + + ", localeList=" + mLocaleList + + ", buffer=" + mBuffer + + "}"; + } +} diff --git a/graphics/java/android/graphics/fonts/FontCustomizationParser.java b/graphics/java/android/graphics/fonts/FontCustomizationParser.java new file mode 100644 index 000000000000..0291d7484dc5 --- /dev/null +++ b/graphics/java/android/graphics/fonts/FontCustomizationParser.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2018 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.graphics.fonts; + +import android.annotation.NonNull; +import android.graphics.FontListParser; +import android.text.FontConfig; +import android.util.Xml; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashSet; + +/** + * Parser for font customization + * + * @hide + */ +public class FontCustomizationParser { + /** + * Represents a customization XML + */ + public static class Result { + ArrayList<FontConfig.Family> mAdditionalNamedFamilies = new ArrayList<>(); + ArrayList<FontConfig.Alias> mAdditionalAliases = new ArrayList<>(); + } + + /** + * Parses the customization XML + * + * Caller must close the input stream + */ + public static Result parse(@NonNull InputStream in, @NonNull String fontDir) + throws XmlPullParserException, IOException { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(in, null); + parser.nextTag(); + return readFamilies(parser, fontDir); + } + + private static void validate(Result result) { + HashSet<String> familyNames = new HashSet<>(); + for (int i = 0; i < result.mAdditionalNamedFamilies.size(); ++i) { + final FontConfig.Family family = result.mAdditionalNamedFamilies.get(i); + final String name = family.getName(); + if (name == null) { + throw new IllegalArgumentException("new-named-family requires name attribute"); + } + if (!familyNames.add(name)) { + throw new IllegalArgumentException( + "new-named-family requires unique name attribute"); + } + } + } + + private static Result readFamilies(XmlPullParser parser, String fontDir) + throws XmlPullParserException, IOException { + Result out = new Result(); + parser.require(XmlPullParser.START_TAG, null, "fonts-modification"); + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() != XmlPullParser.START_TAG) continue; + String tag = parser.getName(); + if (tag.equals("family")) { + readFamily(parser, fontDir, out); + } else if (tag.equals("alias")) { + out.mAdditionalAliases.add(FontListParser.readAlias(parser)); + } else { + FontListParser.skip(parser); + } + } + validate(out); + return out; + } + + private static void readFamily(XmlPullParser parser, String fontDir, Result out) + throws XmlPullParserException, IOException { + final String customizationType = parser.getAttributeValue(null, "customizationType"); + if (customizationType == null) { + throw new IllegalArgumentException("customizationType must be specified"); + } + if (customizationType.equals("new-named-family")) { + out.mAdditionalNamedFamilies.add(FontListParser.readFamily(parser, fontDir)); + } else { + throw new IllegalArgumentException("Unknown customizationType=" + customizationType); + } + } +} diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java new file mode 100644 index 000000000000..75ea12062929 --- /dev/null +++ b/graphics/java/android/graphics/fonts/FontFamily.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2018 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.graphics.fonts; + +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.text.FontConfig; + +import com.android.internal.util.Preconditions; + +import dalvik.annotation.optimization.CriticalNative; + +import libcore.util.NativeAllocationRegistry; + +import java.util.ArrayList; +import java.util.HashSet; + +/** + * A font family class can be used for creating Typeface. + * + * <p> + * A font family is a bundle of fonts for drawing text in various styles. + * For example, you can bundle regular style font and bold style font into a single font family, + * then system will select the correct style font from family for drawing. + * + * <pre> + * FontFamily family = new FontFamily.Builder(new Font.Builder("regular.ttf").build()) + * .addFont(new Font.Builder("bold.ttf").build()).build(); + * Typeface typeface = new Typeface.Builder2(family).build(); + * + * SpannableStringBuilder ssb = new SpannableStringBuilder("Hello, World."); + * ssb.setSpan(new StyleSpan(Typeface.Bold), 6, 12, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + * + * textView.setTypeface(typeface); + * textView.setText(ssb); + * </pre> + * + * In this example, "Hello, " is drawn with "regular.ttf", and "World." is drawn with "bold.ttf". + * + * If there is no font exactly matches with the text style, the system will select the closest font. + * </p> + * + */ +public final class FontFamily { + private static final String TAG = "FontFamily"; + + /** + * A builder class for creating new FontFamily. + */ + public static final class Builder { + private static final NativeAllocationRegistry sFamilyRegistory = + NativeAllocationRegistry.createMalloced(FontFamily.class.getClassLoader(), + nGetReleaseNativeFamily()); + + private final ArrayList<Font> mFonts = new ArrayList<>(); + private final HashSet<Integer> mStyleHashSet = new HashSet<>(); + + /** + * Constructs a builder. + * + * @param font a font + */ + public Builder(@NonNull Font font) { + Preconditions.checkNotNull(font, "font can not be null"); + mStyleHashSet.add(makeStyleIdentifier(font)); + mFonts.add(font); + } + + /** + * Adds different style font to the builder. + * + * System will select the font if the text style is closest to the font. + * If the same style font is already added to the builder, this method will fail with + * {@link IllegalArgumentException}. + * + * Note that system assumes all fonts bundled in FontFamily have the same coverage for the + * code points. For example, regular style font and bold style font must have the same code + * point coverage, otherwise some character may be shown as tofu. + * + * @param font a font + * @return this builder + */ + public @NonNull Builder addFont(@NonNull Font font) { + Preconditions.checkNotNull(font, "font can not be null"); + if (!mStyleHashSet.add(makeStyleIdentifier(font))) { + throw new IllegalArgumentException(font + " has already been added"); + } + mFonts.add(font); + return this; + } + + /** + * Build the font family + * @return a font family + */ + public @NonNull FontFamily build() { + return build("", FontConfig.Family.VARIANT_DEFAULT, true /* isCustomFallback */); + } + + /** @hide */ + public @NonNull FontFamily build(@NonNull String langTags, int variant, + boolean isCustomFallback) { + final long builderPtr = nInitBuilder(); + for (int i = 0; i < mFonts.size(); ++i) { + nAddFont(builderPtr, mFonts.get(i).getNativePtr()); + } + final long ptr = nBuild(builderPtr, langTags, variant, isCustomFallback); + final FontFamily family = new FontFamily(mFonts, ptr); + sFamilyRegistory.registerNativeAllocation(family, ptr); + return family; + } + + private static int makeStyleIdentifier(@NonNull Font font) { + return font.getStyle().getWeight() | (font.getStyle().getSlant() << 16); + } + + private static native long nInitBuilder(); + @CriticalNative + private static native void nAddFont(long builderPtr, long fontPtr); + private static native long nBuild(long builderPtr, String langTags, int variant, + boolean isCustomFallback); + @CriticalNative + private static native long nGetReleaseNativeFamily(); + } + + private final ArrayList<Font> mFonts; + private final long mNativePtr; + + // Use Builder instead. + private FontFamily(@NonNull ArrayList<Font> fonts, long ptr) { + mFonts = fonts; + mNativePtr = ptr; + } + + /** + * Returns a font + * + * @param index an index of the font + * @return a registered font + */ + public @NonNull Font getFont(@IntRange(from = 0) int index) { + return mFonts.get(index); + } + + /** + * Returns the number of fonts in this FontFamily. + * + * @return the number of fonts registered in this family. + */ + public @IntRange(from = 1) int getSize() { + return mFonts.size(); + } + + /** @hide */ + public long getNativePtr() { + return mNativePtr; + } +} diff --git a/graphics/java/android/graphics/fonts/FontFileUtil.java b/graphics/java/android/graphics/fonts/FontFileUtil.java new file mode 100644 index 000000000000..f8b456b982c5 --- /dev/null +++ b/graphics/java/android/graphics/fonts/FontFileUtil.java @@ -0,0 +1,134 @@ +/* + * Copyright 2018 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.graphics.fonts; + +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Provides a utility for font file operations. + * @hide + */ +public class FontFileUtil { + + private FontFileUtil() {} // Do not instanciate + + /** + * Unpack the weight value from packed integer. + */ + public static int unpackWeight(int packed) { + return packed & 0xFFFF; + } + + /** + * Unpack the italic value from packed integer. + */ + public static boolean unpackItalic(int packed) { + return (packed & 0x10000) != 0; + } + + /** + * Returns true if the analyzeStyle succeeded + */ + public static boolean isSuccess(int packed) { + return packed != ANALYZE_ERROR; + } + + private static int pack(@IntRange(from = 0, to = 1000) int weight, boolean italic) { + return weight | (italic ? 0x10000 : 0); + } + + private static final int SFNT_VERSION_1 = 0x00010000; + private static final int SFNT_VERSION_OTTO = 0x4F54544F; + private static final int TTC_TAG = 0x74746366; + private static final int OS2_TABLE_TAG = 0x4F532F32; + + private static final int ANALYZE_ERROR = 0xFFFFFFFF; + + /** + * Analyze the font file returns packed style info + */ + public static final int analyzeStyle(@NonNull ByteBuffer buffer, + @IntRange(from = 0) int ttcIndex, @Nullable FontVariationAxis[] varSettings) { + int weight = -1; + int italic = -1; + if (varSettings != null) { + for (FontVariationAxis axis :varSettings) { + if ("wght".equals(axis.getTag())) { + weight = (int) axis.getStyleValue(); + } else if ("ital".equals(axis.getTag())) { + italic = (axis.getStyleValue() == 1.0f) ? 1 : 0; + } + } + } + + if (weight != -1 && italic != -1) { + // Both weight/italic style are specifeid by variation settings. + // No need to look into OS/2 table. + // TODO: Good to look HVAR table to check if this font supports wght/ital axes. + return pack(weight, italic == 1); + } + + ByteOrder originalOrder = buffer.order(); + buffer.order(ByteOrder.BIG_ENDIAN); + try { + int fontFileOffset = 0; + int magicNumber = buffer.getInt(0); + if (magicNumber == TTC_TAG) { + // TTC file. + if (ttcIndex >= buffer.getInt(8 /* offset to number of fonts in TTC */)) { + return ANALYZE_ERROR; + } + fontFileOffset = buffer.getInt( + 12 /* offset to array of offsets of font files */ + 4 * ttcIndex); + } + int sfntVersion = buffer.getInt(fontFileOffset); + + if (sfntVersion != SFNT_VERSION_1 && sfntVersion != SFNT_VERSION_OTTO) { + return ANALYZE_ERROR; + } + + int numTables = buffer.getShort(fontFileOffset + 4 /* offset to number of tables */); + int os2TableOffset = -1; + for (int i = 0; i < numTables; ++i) { + int tableOffset = fontFileOffset + 12 /* size of offset table */ + + i * 16 /* size of table record */; + if (buffer.getInt(tableOffset) == OS2_TABLE_TAG) { + os2TableOffset = buffer.getInt(tableOffset + 8 /* offset to the table */); + break; + } + } + + if (os2TableOffset == -1) { + // Couldn't find OS/2 table. use regular style + return pack(400, false); + } + + int weightFromOS2 = buffer.getShort(os2TableOffset + 4 /* offset to weight class */); + boolean italicFromOS2 = + (buffer.getShort(os2TableOffset + 62 /* offset to fsSelection */) & 1) != 0; + return pack(weight == -1 ? weightFromOS2 : weight, + italic == -1 ? italicFromOS2 : italic == 1); + } finally { + buffer.order(originalOrder); + } + } +} diff --git a/graphics/java/android/graphics/fonts/FontStyle.java b/graphics/java/android/graphics/fonts/FontStyle.java new file mode 100644 index 000000000000..af517d623b01 --- /dev/null +++ b/graphics/java/android/graphics/fonts/FontStyle.java @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2018 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.graphics.fonts; + +import android.annotation.IntDef; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; + +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * A font style object. + * + * This class represents a single font style which is a pair of weight value and slant value. + * Here are common font styles examples: + * <p> + * <pre> + * <code> + * final FontStyle NORMAL = new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT); + * final FontStyle BOLD = new FontStyle(FONT_WEIGHT_BOLD, FONT_SLANT_UPRIGHT); + * final FontStyle ITALIC = new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_ITALIC); + * final FontStyle BOLD_ITALIC = new FontStyle(FONT_WEIGHT_BOLD, FONT_SLANT_ITALIC); + * </code> + * </pre> + * </p> + * + */ +public final class FontStyle { + private static final String TAG = "FontStyle"; + + /** + * A minimum weight value for the font + */ + public static final int FONT_WEIGHT_MIN = 1; + + /** + * A font weight value for the thin weight + */ + public static final int FONT_WEIGHT_THIN = 100; + + /** + * A font weight value for the extra-light weight + */ + public static final int FONT_WEIGHT_EXTRA_LIGHT = 200; + + /** + * A font weight value for the light weight + */ + public static final int FONT_WEIGHT_LIGHT = 300; + + /** + * A font weight value for the normal weight + */ + public static final int FONT_WEIGHT_NORMAL = 400; + + /** + * A font weight value for the medium weight + */ + public static final int FONT_WEIGHT_MEDIUM = 500; + + /** + * A font weight value for the semi-bold weight + */ + public static final int FONT_WEIGHT_SEMI_BOLD = 600; + + /** + * A font weight value for the bold weight. + */ + public static final int FONT_WEIGHT_BOLD = 700; + + /** + * A font weight value for the extra-bold weight + */ + public static final int FONT_WEIGHT_EXTRA_BOLD = 800; + + /** + * A font weight value for the black weight + */ + public static final int FONT_WEIGHT_BLACK = 900; + + /** + * A maximum weight value for the font + */ + public static final int FONT_WEIGHT_MAX = 1000; + + /** + * A font slant value for upright + */ + public static final int FONT_SLANT_UPRIGHT = 0; + + /** + * A font slant value for italic + */ + public static final int FONT_SLANT_ITALIC = 1; + + // TODO: Support FONT_SLANT_OBLIQUE + + /** @hide */ + @IntDef(prefix = { "FONT_SLANT_" }, value = { + FONT_SLANT_UPRIGHT, + FONT_SLANT_ITALIC + }) + @Retention(RetentionPolicy.SOURCE) + public @interface FontSlant {} + + private final @IntRange(from = 0, to = 1000) int mWeight; + private final @FontSlant int mSlant; + // TODO: Support width + + public FontStyle() { + mWeight = FONT_WEIGHT_NORMAL; + mSlant = FONT_SLANT_UPRIGHT; + } + + /** + * Create FontStyle with specific weight and italic + * + * <p> + * <table> + * <thead> + * <tr> + * <th align="center">Value</th> + * <th align="center">Name</th> + * <th align="center">Android Definition</th> + * </tr> + * </thead> + * <tbody> + * <tr> + * <td align="center">100</td> + * <td align="center">Thin</td> + * <td align="center">{@link FontStyle#FONT_WEIGHT_THIN}</td> + * </tr> + * <tr> + * <td align="center">200</td> + * <td align="center">Extra Light (Ultra Light)</td> + * <td align="center">{@link FontStyle#FONT_WEIGHT_EXTRA_LIGHT}</td> + * </tr> + * <tr> + * <td align="center">300</td> + * <td align="center">Light</td> + * <td align="center">{@link FontStyle#FONT_WEIGHT_LIGHT}</td> + * </tr> + * <tr> + * <td align="center">400</td> + * <td align="center">Normal (Regular)</td> + * <td align="center">{@link FontStyle#FONT_WEIGHT_NORMAL}</td> + * </tr> + * <tr> + * <td align="center">500</td> + * <td align="center">Medium</td> + * <td align="center">{@link FontStyle#FONT_WEIGHT_MEDIUM}</td> + * </tr> + * <tr> + * <td align="center">600</td> + * <td align="center">Semi Bold (Demi Bold)</td> + * <td align="center">{@link FontStyle#FONT_WEIGHT_SEMI_BOLD}</td> + * </tr> + * <tr> + * <td align="center">700</td> + * <td align="center">Bold</td> + * <td align="center">{@link FontStyle#FONT_WEIGHT_BOLD}</td> + * </tr> + * <tr> + * <td align="center">800</td> + * <td align="center">Extra Bold (Ultra Bold)</td> + * <td align="center">{@link FontStyle#FONT_WEIGHT_EXTRA_BOLD}</td> + * </tr> + * <tr> + * <td align="center">900</td> + * <td align="center">Black (Heavy)</td> + * <td align="center">{@link FontStyle#FONT_WEIGHT_BLACK}</td> + * </tr> + * </tbody> + * </p> + * + * @see FontStyle#FONT_WEIGHT_THIN + * @see FontStyle#FONT_WEIGHT_EXTRA_LIGHT + * @see FontStyle#FONT_WEIGHT_LIGHT + * @see FontStyle#FONT_WEIGHT_NORMAL + * @see FontStyle#FONT_WEIGHT_MEDIUM + * @see FontStyle#FONT_WEIGHT_SEMI_BOLD + * @see FontStyle#FONT_WEIGHT_BOLD + * @see FontStyle#FONT_WEIGHT_EXTRA_BOLD + * @see FontStyle#FONT_WEIGHT_BLACK + * @param weight a weight value + * @param slant a slant value + */ + public FontStyle(int weight, @FontSlant int slant) { + Preconditions.checkArgument(FONT_WEIGHT_MIN <= weight && weight <= FONT_WEIGHT_MAX, + "weight value must be [" + FONT_WEIGHT_MIN + ", " + FONT_WEIGHT_MAX + "]"); + Preconditions.checkArgument(slant == FONT_SLANT_UPRIGHT || slant == FONT_SLANT_ITALIC, + "slant value must be FONT_SLANT_UPRIGHT or FONT_SLANT_UPRIGHT"); + mWeight = weight; + mSlant = slant; + } + + + /** + * Gets the weight value + * + * @see FontStyle#setWeight(int) + * @return a weight value + */ + public @IntRange(from = 0, to = 1000) int getWeight() { + return mWeight; + } + + /** + * Gets the slant value + * + * @return a slant value + */ + public @FontSlant int getSlant() { + return mSlant; + } + + /** + * Compute the matching score for another style. + * + * The smaller is better. + * @hide + */ + public int getMatchScore(@NonNull FontStyle o) { + return Math.abs((getWeight() - o.getWeight())) / 100 + (getSlant() == o.getSlant() ? 0 : 2); + } + + @Override + public boolean equals(@Nullable Object o) { + if (o == this) { + return true; + } + if (o == null || !(o instanceof FontStyle)) { + return false; + } + FontStyle fontStyle = (FontStyle) o; + return fontStyle.mWeight == mWeight && fontStyle.mSlant == mSlant; + } + + @Override + public int hashCode() { + return Objects.hash(mWeight, mSlant); + } + + @Override + public String toString() { + return "FontStyle { weight=" + mWeight + ", slant=" + mSlant + "}"; + } +} diff --git a/graphics/java/android/graphics/fonts/FontVariationAxis.java b/graphics/java/android/graphics/fonts/FontVariationAxis.java index 1b7408a03294..bcee559d8291 100644 --- a/graphics/java/android/graphics/fonts/FontVariationAxis.java +++ b/graphics/java/android/graphics/fonts/FontVariationAxis.java @@ -18,17 +18,22 @@ package android.graphics.fonts; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UnsupportedAppUsage; +import android.os.Build; import android.text.TextUtils; import java.util.ArrayList; +import java.util.Objects; import java.util.regex.Pattern; /** * Class that holds information about single font variation axis. */ public final class FontVariationAxis { + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private final int mTag; private final String mTagString; + @UnsupportedAppUsage private final float mStyleValue; /** @@ -183,5 +188,22 @@ public final class FontVariationAxis { } return TextUtils.join(",", axes); } + + @Override + public boolean equals(@Nullable Object o) { + if (o == this) { + return true; + } + if (o == null || !(o instanceof FontVariationAxis)) { + return false; + } + FontVariationAxis axis = (FontVariationAxis) o; + return axis.mTag == mTag && axis.mStyleValue == mStyleValue; + } + + @Override + public int hashCode() { + return Objects.hash(mTag, mStyleValue); + } } diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java new file mode 100644 index 000000000000..4a9cf14d04a5 --- /dev/null +++ b/graphics/java/android/graphics/fonts/SystemFonts.java @@ -0,0 +1,319 @@ +/* + * Copyright 2018 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.graphics.fonts; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.FontListParser; +import android.text.FontConfig; +import android.util.ArrayMap; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Provides the system font configurations. + */ +public final class SystemFonts { + private static final String TAG = "SystemFonts"; + private static final String DEFAULT_FAMILY = "sans-serif"; + + private SystemFonts() {} // Do not instansiate. + + private static final Map<String, FontFamily[]> sSystemFallbackMap; + private static final FontConfig.Alias[] sAliases; + private static final List<Font> sAvailableFonts; + + /** + * Returns all available font files in the system. + * + * @return a set of system fonts + */ + public static @NonNull Set<Font> getAvailableFonts() { + HashSet<Font> set = new HashSet<>(); + set.addAll(sAvailableFonts); + return set; + } + + /** + * Returns fallback list for the given family name. + * + * If no fallback found for the given family name, returns fallback for the default family. + * + * @param familyName family name, e.g. "serif" + * @hide + */ + public static @NonNull FontFamily[] getSystemFallback(@Nullable String familyName) { + final FontFamily[] families = sSystemFallbackMap.get(familyName); + return families == null ? sSystemFallbackMap.get(DEFAULT_FAMILY) : families; + } + + /** + * Returns raw system fallback map. + * + * This method is intended to be used only by Typeface static initializer. + * @hide + */ + public static @NonNull Map<String, FontFamily[]> getRawSystemFallbackMap() { + return sSystemFallbackMap; + } + + /** + * Returns a list of aliases. + * + * This method is intended to be used only by Typeface static initializer. + * @hide + */ + public static @NonNull FontConfig.Alias[] getAliases() { + return sAliases; + } + + private static @Nullable ByteBuffer mmap(@NonNull String fullPath) { + try (FileInputStream file = new FileInputStream(fullPath)) { + final FileChannel fileChannel = file.getChannel(); + final long fontSize = fileChannel.size(); + return fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize); + } catch (IOException e) { + Log.e(TAG, "Error mapping font file " + fullPath); + return null; + } + } + + private static void pushFamilyToFallback(@NonNull FontConfig.Family xmlFamily, + @NonNull ArrayMap<String, ArrayList<FontFamily>> fallbackMap, + @NonNull Map<String, ByteBuffer> cache, + @NonNull ArrayList<Font> availableFonts) { + + final String languageTags = xmlFamily.getLanguages(); + final int variant = xmlFamily.getVariant(); + + final ArrayList<FontConfig.Font> defaultFonts = new ArrayList<>(); + final ArrayMap<String, ArrayList<FontConfig.Font>> specificFallbackFonts = new ArrayMap<>(); + + // Collect default fallback and specific fallback fonts. + for (final FontConfig.Font font : xmlFamily.getFonts()) { + final String fallbackName = font.getFallbackFor(); + if (fallbackName == null) { + defaultFonts.add(font); + } else { + ArrayList<FontConfig.Font> fallback = specificFallbackFonts.get(fallbackName); + if (fallback == null) { + fallback = new ArrayList<>(); + specificFallbackFonts.put(fallbackName, fallback); + } + fallback.add(font); + } + } + + final FontFamily defaultFamily = defaultFonts.isEmpty() ? null : createFontFamily( + xmlFamily.getName(), defaultFonts, languageTags, variant, cache, availableFonts); + + // Insert family into fallback map. + for (int i = 0; i < fallbackMap.size(); i++) { + final ArrayList<FontConfig.Font> fallback = + specificFallbackFonts.get(fallbackMap.keyAt(i)); + if (fallback == null) { + if (defaultFamily != null) { + fallbackMap.valueAt(i).add(defaultFamily); + } + } else { + final FontFamily family = createFontFamily( + xmlFamily.getName(), fallback, languageTags, variant, cache, + availableFonts); + if (family != null) { + fallbackMap.valueAt(i).add(family); + } else if (defaultFamily != null) { + fallbackMap.valueAt(i).add(defaultFamily); + } else { + // There is no valid for for default fallback. Ignore. + } + } + } + } + + private static @Nullable FontFamily createFontFamily(@NonNull String familyName, + @NonNull List<FontConfig.Font> fonts, + @NonNull String languageTags, + @FontConfig.Family.Variant int variant, + @NonNull Map<String, ByteBuffer> cache, + @NonNull ArrayList<Font> availableFonts) { + if (fonts.size() == 0) { + return null; + } + + FontFamily.Builder b = null; + for (int i = 0; i < fonts.size(); i++) { + final FontConfig.Font fontConfig = fonts.get(i); + final String fullPath = fontConfig.getFontName(); + ByteBuffer buffer = cache.get(fullPath); + if (buffer == null) { + if (cache.containsKey(fullPath)) { + continue; // Already failed to mmap. Skip it. + } + buffer = mmap(fullPath); + cache.put(fullPath, buffer); + if (buffer == null) { + continue; + } + } + + final Font font; + try { + font = new Font.Builder(buffer, new File(fullPath), languageTags) + .setWeight(fontConfig.getWeight()) + .setSlant(fontConfig.isItalic() ? FontStyle.FONT_SLANT_ITALIC + : FontStyle.FONT_SLANT_UPRIGHT) + .setTtcIndex(fontConfig.getTtcIndex()) + .setFontVariationSettings(fontConfig.getAxes()) + .build(); + } catch (IOException e) { + throw new RuntimeException(e); // Never reaches here + } + + availableFonts.add(font); + if (b == null) { + b = new FontFamily.Builder(font); + } else { + b.addFont(font); + } + } + return b == null ? null : b.build(languageTags, variant, false /* isCustomFallback */); + } + + private static void appendNamedFamily(@NonNull FontConfig.Family xmlFamily, + @NonNull HashMap<String, ByteBuffer> bufferCache, + @NonNull ArrayMap<String, ArrayList<FontFamily>> fallbackListMap, + @NonNull ArrayList<Font> availableFonts) { + final String familyName = xmlFamily.getName(); + final FontFamily family = createFontFamily( + familyName, Arrays.asList(xmlFamily.getFonts()), + xmlFamily.getLanguages(), xmlFamily.getVariant(), bufferCache, availableFonts); + if (family == null) { + return; + } + final ArrayList<FontFamily> fallback = new ArrayList<>(); + fallback.add(family); + fallbackListMap.put(familyName, fallback); + } + + /** + * Build the system fallback from xml file. + * + * @param xmlPath A full path string to the fonts.xml file. + * @param fontDir A full path string to the system font directory. This must end with + * slash('/'). + * @param fallbackMap An output system fallback map. Caller must pass empty map. + * @return a list of aliases + * @hide + */ + @VisibleForTesting + public static FontConfig.Alias[] buildSystemFallback(@NonNull String xmlPath, + @NonNull String fontDir, + @NonNull FontCustomizationParser.Result oemCustomization, + @NonNull ArrayMap<String, FontFamily[]> fallbackMap, + @NonNull ArrayList<Font> availableFonts) { + try { + final FileInputStream fontsIn = new FileInputStream(xmlPath); + final FontConfig fontConfig = FontListParser.parse(fontsIn, fontDir); + + final HashMap<String, ByteBuffer> bufferCache = new HashMap<String, ByteBuffer>(); + final FontConfig.Family[] xmlFamilies = fontConfig.getFamilies(); + + final ArrayMap<String, ArrayList<FontFamily>> fallbackListMap = new ArrayMap<>(); + // First traverse families which have a 'name' attribute to create fallback map. + for (final FontConfig.Family xmlFamily : xmlFamilies) { + final String familyName = xmlFamily.getName(); + if (familyName == null) { + continue; + } + appendNamedFamily(xmlFamily, bufferCache, fallbackListMap, availableFonts); + } + + for (int i = 0; i < oemCustomization.mAdditionalNamedFamilies.size(); ++i) { + appendNamedFamily(oemCustomization.mAdditionalNamedFamilies.get(i), + bufferCache, fallbackListMap, availableFonts); + } + + // Then, add fallback fonts to the each fallback map. + for (int i = 0; i < xmlFamilies.length; i++) { + final FontConfig.Family xmlFamily = xmlFamilies[i]; + // The first family (usually the sans-serif family) is always placed immediately + // after the primary family in the fallback. + if (i == 0 || xmlFamily.getName() == null) { + pushFamilyToFallback(xmlFamily, fallbackListMap, bufferCache, availableFonts); + } + } + + // Build the font map and fallback map. + for (int i = 0; i < fallbackListMap.size(); i++) { + final String fallbackName = fallbackListMap.keyAt(i); + final List<FontFamily> familyList = fallbackListMap.valueAt(i); + final FontFamily[] families = familyList.toArray(new FontFamily[familyList.size()]); + + fallbackMap.put(fallbackName, families); + } + + final ArrayList<FontConfig.Alias> list = new ArrayList<>(); + list.addAll(Arrays.asList(fontConfig.getAliases())); + list.addAll(oemCustomization.mAdditionalAliases); + return list.toArray(new FontConfig.Alias[list.size()]); + } catch (IOException | XmlPullParserException e) { + Log.e(TAG, "Failed initialize system fallbacks.", e); + return ArrayUtils.emptyArray(FontConfig.Alias.class); + } + } + + private static FontCustomizationParser.Result readFontCustomization( + @NonNull String customizeXml, @NonNull String customFontsDir) { + try (FileInputStream f = new FileInputStream(customizeXml)) { + return FontCustomizationParser.parse(f, customFontsDir); + } catch (IOException e) { + return new FontCustomizationParser.Result(); + } catch (XmlPullParserException e) { + Log.e(TAG, "Failed to parse font customization XML", e); + return new FontCustomizationParser.Result(); + } + } + + static { + final ArrayMap<String, FontFamily[]> systemFallbackMap = new ArrayMap<>(); + final ArrayList<Font> availableFonts = new ArrayList<>(); + final FontCustomizationParser.Result oemCustomization = + readFontCustomization("/product/etc/fonts_customization.xml", "/product/fonts/"); + sAliases = buildSystemFallback("/system/etc/fonts.xml", "/system/fonts/", + oemCustomization, systemFallbackMap, availableFonts); + sSystemFallbackMap = Collections.unmodifiableMap(systemFallbackMap); + sAvailableFonts = Collections.unmodifiableList(availableFonts); + } +} diff --git a/graphics/java/android/graphics/pdf/PdfEditor.java b/graphics/java/android/graphics/pdf/PdfEditor.java index 3821bc7ab063..21ce1b8392d2 100644 --- a/graphics/java/android/graphics/pdf/PdfEditor.java +++ b/graphics/java/android/graphics/pdf/PdfEditor.java @@ -27,7 +27,6 @@ import android.system.Os; import android.system.OsConstants; import dalvik.system.CloseGuard; import libcore.io.IoUtils; -import libcore.io.Libcore; import java.io.IOException; diff --git a/graphics/java/android/graphics/pdf/PdfRenderer.java b/graphics/java/android/graphics/pdf/PdfRenderer.java index 4a91705239c1..bd1a49205fd5 100644 --- a/graphics/java/android/graphics/pdf/PdfRenderer.java +++ b/graphics/java/android/graphics/pdf/PdfRenderer.java @@ -19,6 +19,7 @@ package android.graphics.pdf; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UnsupportedAppUsage; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.Matrix; @@ -118,6 +119,7 @@ public final class PdfRenderer implements AutoCloseable { private ParcelFileDescriptor mInput; + @UnsupportedAppUsage private Page mCurrentPage; /** @hide */ @@ -242,6 +244,7 @@ public final class PdfRenderer implements AutoCloseable { } } + @UnsupportedAppUsage private void doClose() { if (mCurrentPage != null) { mCurrentPage.close(); @@ -432,8 +435,9 @@ public final class PdfRenderer implements AutoCloseable { final long transformPtr = transform.native_instance; synchronized (sPdfiumLock) { - nativeRenderPage(mNativeDocument, mNativePage, destination, contentLeft, - contentTop, contentRight, contentBottom, transformPtr, renderMode); + nativeRenderPage(mNativeDocument, mNativePage, destination.getNativeInstance(), + contentLeft, contentTop, contentRight, contentBottom, transformPtr, + renderMode); } } @@ -484,7 +488,7 @@ public final class PdfRenderer implements AutoCloseable { private static native void nativeClose(long documentPtr); private static native int nativeGetPageCount(long documentPtr); private static native boolean nativeScaleForPrinting(long documentPtr); - private static native void nativeRenderPage(long documentPtr, long pagePtr, Bitmap dest, + private static native void nativeRenderPage(long documentPtr, long pagePtr, long bitmapHandle, int clipLeft, int clipTop, int clipRight, int clipBottom, long transformPtr, int renderMode); private static native long nativeOpenPageAndGetSize(long documentPtr, int pageIndex, diff --git a/graphics/java/android/graphics/text/LineBreaker.java b/graphics/java/android/graphics/text/LineBreaker.java new file mode 100644 index 000000000000..54622c5e74df --- /dev/null +++ b/graphics/java/android/graphics/text/LineBreaker.java @@ -0,0 +1,537 @@ +/* + * Copyright (C) 2018 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.graphics.text; + +import android.annotation.FloatRange; +import android.annotation.IntDef; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.Px; + +import dalvik.annotation.optimization.CriticalNative; +import dalvik.annotation.optimization.FastNative; + +import libcore.util.NativeAllocationRegistry; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Provides automatic line breaking for a <em>single</em> paragraph. + * + * <p> + * <pre> + * <code> + * Paint paint = new Paint(); + * Paint bigPaint = new Paint(); + * bigPaint.setTextSize(paint.getTextSize() * 2.0); + * String text = "Hello, Android."; + * + * // Prepare the measured text + * MeasuredText mt = new MeasuredText.Builder(text.toCharArray()) + * .appendStyleRun(paint, 7, false) // Use paint for "Hello, " + * .appednStyleRun(bigPaint, 8, false) // Use bigPaint for "Hello, " + * .build(); + * + * LineBreaker lb = new LineBreaker.Builder() + * // Use simple line breaker + * .setBreakStrategy(LineBreaker.BREAK_STRATEGY_SIMPLE) + * // Do not add hyphenation. + * .setHyphenationFrequency(LineBreaker.HYPHENATION_FREQUENCY_NONE) + * // Build the LineBreaker + * .build(); + * + * ParagraphConstraints c = new ParagraphConstraints(); + * c.setWidth(240); // Set the line wieth as 1024px + * + * // Do the line breaking + * Result r = lb.computeLineBreaks(mt, c, 0); + * + * // Compute the total height of the text. + * float totalHeight = 0; + * for (int i = 0; i < r.getLineCount(); ++i) { // iterate over the lines + * totalHeight += r.getLineDescent(i) - r.getLineAscent(i); + * } + * + * // Draw text to the canvas + * Bitmap bmp = Bitmap.createBitmap(240, totalHeight, Bitmap.Config.ARGB_8888); + * Canvas c = new Canvas(bmp); + * float yOffset = 0f; + * int prevOffset = 0; + * for (int i = 0; i < r.getLineCount(); ++i) { // iterate over the lines + * int nextOffset = r.getLineBreakOffset(i); + * c.drawText(text, prevOffset, nextOffset, 0f, yOffset, paint); + * + * prevOffset = nextOffset; + * yOffset += r.getLineDescent(i) - r.getLineAscent(i); + * } + * </code> + * </pre> + * </p> + */ +public class LineBreaker { + /** @hide */ + @IntDef(prefix = { "BREAK_STRATEGY_" }, value = { + BREAK_STRATEGY_SIMPLE, + BREAK_STRATEGY_HIGH_QUALITY, + BREAK_STRATEGY_BALANCED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface BreakStrategy {} + + /** + * Value for break strategy indicating simple line breaking. + * + * The line breaker puts words to the line as much as possible and breaks line if no more words + * can fit into the same line. Automatic hyphens are only added when a line has a single word + * and that word is longer than line width. This is the fastest break strategy and ideal for + * editor. + */ + public static final int BREAK_STRATEGY_SIMPLE = 0; + + /** + * Value for break strategy indicating high quality line breaking. + * + * With this option line breaker does whole-paragraph optimization for more readable text, and + * also applies automatic hyphenation when required. + */ + public static final int BREAK_STRATEGY_HIGH_QUALITY = 1; + + /** + * Value for break strategy indicating balanced line breaking. + * + * The line breaker does whole-paragraph optimization for making all lines similar length, and + * also applies automatic hyphenation when required. This break strategy is good for small + * screen devices such as watch screens. + */ + public static final int BREAK_STRATEGY_BALANCED = 2; + + /** @hide */ + @IntDef(prefix = { "HYPHENATION_FREQUENCY_" }, value = { + HYPHENATION_FREQUENCY_NORMAL, + HYPHENATION_FREQUENCY_FULL, + HYPHENATION_FREQUENCY_NONE + }) + @Retention(RetentionPolicy.SOURCE) + public @interface HyphenationFrequency {} + + /** + * Value for hyphenation frequency indicating no automatic hyphenation. + * + * Using this option disables auto hyphenation which results in better text layout performance. + * A word may be broken without hyphens when a line has a single word and that word is longer + * than line width. Soft hyphens are ignored and will not be used as suggestions for potential + * line breaks. + */ + public static final int HYPHENATION_FREQUENCY_NONE = 0; + + /** + * Value for hyphenation frequency indicating a light amount of automatic hyphenation. + * + * This hyphenation frequency is useful for informal cases, such as short sentences or chat + * messages. + */ + public static final int HYPHENATION_FREQUENCY_NORMAL = 1; + + /** + * Value for hyphenation frequency indicating the full amount of automatic hyphenation. + * + * This hyphenation frequency is useful for running text and where it's important to put the + * maximum amount of text in a screen with limited space. + */ + public static final int HYPHENATION_FREQUENCY_FULL = 2; + + /** @hide */ + @IntDef(prefix = { "JUSTIFICATION_MODE_" }, value = { + JUSTIFICATION_MODE_NONE, + JUSTIFICATION_MODE_INTER_WORD + }) + @Retention(RetentionPolicy.SOURCE) + public @interface JustificationMode {} + + /** + * Value for justification mode indicating no justification. + */ + public static final int JUSTIFICATION_MODE_NONE = 0; + + /** + * Value for justification mode indicating the text is justified by stretching word spacing. + */ + public static final int JUSTIFICATION_MODE_INTER_WORD = 1; + + /** + * Helper class for creating a {@link LineBreaker}. + */ + public static final class Builder { + private @BreakStrategy int mBreakStrategy = BREAK_STRATEGY_SIMPLE; + private @HyphenationFrequency int mHyphenationFrequency = HYPHENATION_FREQUENCY_NONE; + private @JustificationMode int mJustificationMode = JUSTIFICATION_MODE_NONE; + private @Nullable int[] mIndents = null; + + /** + * Set break strategy. + * + * You can change the line breaking behavior by setting break strategy. The default value is + * {@link #BREAK_STRATEGY_SIMPLE}. + */ + public @NonNull Builder setBreakStrategy(@BreakStrategy int breakStrategy) { + mBreakStrategy = breakStrategy; + return this; + } + + /** + * Set hyphenation frequency. + * + * You can change the amount of automatic hyphenation used. The default value is + * {@link #HYPHENATION_FREQUENCY_NONE}. + */ + public @NonNull Builder setHyphenationFrequency( + @HyphenationFrequency int hyphenationFrequency) { + mHyphenationFrequency = hyphenationFrequency; + return this; + } + + /** + * Set whether the text is justified. + * + * By setting {@link #JUSTIFICATION_MODE_INTER_WORD}, the line breaker will change the + * internal parameters for justification. + * The default value is {@link #JUSTIFICATION_MODE_NONE} + */ + public @NonNull Builder setJustificationMode(@JustificationMode int justificationMode) { + mJustificationMode = justificationMode; + return this; + } + + /** + * Set indents. + * + * The supplied array provides the total amount of indentation per line, in pixel. This + * amount is the sum of both left and right indentations. For lines past the last element in + * the array, the indentation amount of the last element is used. + */ + public @NonNull Builder setIndents(@Nullable int[] indents) { + mIndents = indents; + return this; + } + + /** + * Build a new LineBreaker with given parameters. + * + * You can reuse the Builder instance even after calling this method. + */ + public @NonNull LineBreaker build() { + return new LineBreaker(mBreakStrategy, mHyphenationFrequency, mJustificationMode, + mIndents); + } + } + + /** + * Line breaking constraints for single paragraph. + */ + public static class ParagraphConstraints { + private @FloatRange(from = 0.0f) float mWidth = 0; + private @FloatRange(from = 0.0f) float mFirstWidth = 0; + private @IntRange(from = 0) int mFirstWidthLineCount = 0; + private @Nullable float[] mVariableTabStops = null; + private @FloatRange(from = 0) float mDefaultTabStop = 0; + + public ParagraphConstraints() {} + + /** + * Set width for this paragraph. + * + * @see #getWidth() + */ + public void setWidth(@Px @FloatRange(from = 0.0f) float width) { + mWidth = width; + } + + /** + * Set indent for this paragraph. + * + * @param firstWidth the line width of the starting of the paragraph + * @param firstWidthLineCount the number of lines that applies the firstWidth + * @see #getFirstWidth() + * @see #getFirstWidthLineCount() + */ + public void setIndent(@Px @FloatRange(from = 0.0f) float firstWidth, + @Px @IntRange(from = 0) int firstWidthLineCount) { + mFirstWidth = firstWidth; + mFirstWidthLineCount = firstWidthLineCount; + } + + /** + * Set tab stops for this paragraph. + * + * @param tabStops the array of pixels of tap stopping position + * @param defaultTabStop pixels of the default tab stopping position + * @see #getTabStops() + * @see #getDefaultTabStop() + */ + public void setTabStops(@Nullable float[] tabStops, + @Px @FloatRange(from = 0) float defaultTabStop) { + mVariableTabStops = tabStops; + mDefaultTabStop = defaultTabStop; + } + + /** + * Return the width for this paragraph in pixels. + * + * @see #setWidth(float) + */ + public @Px @FloatRange(from = 0.0f) float getWidth() { + return mWidth; + } + + /** + * Return the first line's width for this paragraph in pixel. + * + * @see #setIndent(float, int) + */ + public @Px @FloatRange(from = 0.0f) float getFirstWidth() { + return mFirstWidth; + } + + /** + * Return the number of lines to apply the first line's width. + * + * @see #setIndent(float, int) + */ + public @Px @IntRange(from = 0) int getFirstWidthLineCount() { + return mFirstWidthLineCount; + } + + /** + * Returns the array of tab stops in pixels. + * + * @see #setTabStops(float[], int) + */ + public @Nullable float[] getTabStops() { + return mVariableTabStops; + } + + /** + * Returns the default tab stops in pixels. + * + * @see #setTabStop(float[], int) + */ + public @Px @FloatRange(from = 0) float getDefaultTabStop() { + return mDefaultTabStop; + } + } + + /** + * Holds the result of the {@link LineBreaker#computeLineBreaks line breaking algorithm}. + * @see LineBreaker#computeLineBreaks + */ + public static class Result { + // Following two contstant must be synced with minikin's line breaker. + // TODO(nona): Remove these constatns by introducing native methods. + private static final int TAB_MASK = 0x20000000; + private static final int HYPHEN_MASK = 0xFF; + private static final int START_HYPHEN_MASK = 0x18; // 0b11000 + private static final int END_HYPHEN_MASK = 0x7; // 0b00111 + private static final int START_HYPHEN_BITS_SHIFT = 3; + + private static final NativeAllocationRegistry sRegistry = + NativeAllocationRegistry.createMalloced( + Result.class.getClassLoader(), nGetReleaseResultFunc()); + private final long mPtr; + + private Result(long ptr) { + mPtr = ptr; + sRegistry.registerNativeAllocation(this, mPtr); + } + + /** + * Returns the number of lines in the paragraph. + * + * @return number of lines + */ + public @IntRange(from = 0) int getLineCount() { + return nGetLineCount(mPtr); + } + + /** + * Returns character offset of the break for a given line. + * + * @param lineIndex an index of the line. + * @return the break offset. + */ + public @IntRange(from = 0) int getLineBreakOffset(@IntRange(from = 0) int lineIndex) { + return nGetLineBreakOffset(mPtr, lineIndex); + } + + /** + * Returns width of a given line in pixels. + * + * @param lineIndex an index of the line. + * @return width of the line in pixels + */ + public @Px float getLineWidth(@IntRange(from = 0) int lineIndex) { + return nGetLineWidth(mPtr, lineIndex); + } + + /** + * Returns font ascent of the line in pixels. + * + * @param lineIndex an index of the line. + * @return an entier font ascent of the line in pixels. + */ + public @Px float getLineAscent(@IntRange(from = 0) int lineIndex) { + return nGetLineAscent(mPtr, lineIndex); + } + + /** + * Returns font descent of the line in pixels. + * + * @param lineIndex an index of the line. + * @return an entier font descent of the line in pixels. + */ + public @Px float getLineDescent(@IntRange(from = 0) int lineIndex) { + return nGetLineDescent(mPtr, lineIndex); + } + + /** + * Returns true if the line has a TAB character. + * + * @param lineIndex an index of the line. + * @return true if the line has a TAB character + */ + public boolean hasLineTab(int lineIndex) { + return (nGetLineFlag(mPtr, lineIndex) & TAB_MASK) != 0; + } + + /** + * Returns a start hyphen edit for the line. + * + * @param lineIndex an index of the line. + * @return a start hyphen edit for the line. + * + * @see android.graphics.Paint#setStartHyphenEdit + * @see android.graphics.Paint#getStartHyphenEdit + */ + public int getStartLineHyphenEdit(int lineIndex) { + return (nGetLineFlag(mPtr, lineIndex) & START_HYPHEN_MASK) >> START_HYPHEN_BITS_SHIFT; + } + + /** + * Returns an end hyphen edit for the line. + * + * @param lineIndex an index of the line. + * @return an end hyphen edit for the line. + * + * @see android.graphics.Paint#setEndHyphenEdit + * @see android.graphics.Paint#getEndHyphenEdit + */ + public int getEndLineHyphenEdit(int lineIndex) { + return nGetLineFlag(mPtr, lineIndex) & END_HYPHEN_MASK; + } + } + + private static final NativeAllocationRegistry sRegistry = + NativeAllocationRegistry.createMalloced( + LineBreaker.class.getClassLoader(), nGetReleaseFunc()); + + private final long mNativePtr; + + /** + * Use Builder instead. + */ + private LineBreaker(@BreakStrategy int breakStrategy, + @HyphenationFrequency int hyphenationFrequency, @JustificationMode int justify, + @Nullable int[] indents) { + mNativePtr = nInit(breakStrategy, hyphenationFrequency, + justify == JUSTIFICATION_MODE_INTER_WORD, indents); + sRegistry.registerNativeAllocation(this, mNativePtr); + } + + /** + * Break paragraph into lines. + * + * The result is filled to out param. + * + * @param measuredPara a result of the text measurement + * @param constraints for a single paragraph + * @param lineNumber a line number of this paragraph + */ + public @NonNull Result computeLineBreaks( + @NonNull MeasuredText measuredPara, + @NonNull ParagraphConstraints constraints, + @IntRange(from = 0) int lineNumber) { + return new Result(nComputeLineBreaks( + mNativePtr, + + // Inputs + measuredPara.getChars(), + measuredPara.getNativePtr(), + measuredPara.getChars().length, + constraints.mFirstWidth, + constraints.mFirstWidthLineCount, + constraints.mWidth, + constraints.mVariableTabStops, + constraints.mDefaultTabStop, + lineNumber)); + } + + @FastNative + private static native long nInit(@BreakStrategy int breakStrategy, + @HyphenationFrequency int hyphenationFrequency, boolean isJustified, + @Nullable int[] indents); + + @CriticalNative + private static native long nGetReleaseFunc(); + + // populates LineBreaks and returns the number of breaks found + // + // the arrays inside the LineBreaks objects are passed in as well + // to reduce the number of JNI calls in the common case where the + // arrays do not have to be resized + // The individual character widths will be returned in charWidths. The length of + // charWidths must be at least the length of the text. + private static native long nComputeLineBreaks( + /* non zero */ long nativePtr, + + // Inputs + @NonNull char[] text, + /* Non Zero */ long measuredTextPtr, + @IntRange(from = 0) int length, + @FloatRange(from = 0.0f) float firstWidth, + @IntRange(from = 0) int firstWidthLineCount, + @FloatRange(from = 0.0f) float restWidth, + @Nullable float[] variableTabStops, + float defaultTabStop, + @IntRange(from = 0) int indentsOffset); + + // Result accessors + @CriticalNative + private static native int nGetLineCount(long ptr); + @CriticalNative + private static native int nGetLineBreakOffset(long ptr, int idx); + @CriticalNative + private static native float nGetLineWidth(long ptr, int idx); + @CriticalNative + private static native float nGetLineAscent(long ptr, int idx); + @CriticalNative + private static native float nGetLineDescent(long ptr, int idx); + @CriticalNative + private static native int nGetLineFlag(long ptr, int idx); + @CriticalNative + private static native long nGetReleaseResultFunc(); +} diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java new file mode 100644 index 000000000000..b6d8fa19fca8 --- /dev/null +++ b/graphics/java/android/graphics/text/MeasuredText.java @@ -0,0 +1,385 @@ +/* + * Copyright (C) 2010 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.graphics.text; + +import android.annotation.FloatRange; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.Px; +import android.graphics.Paint; +import android.graphics.Rect; + +import com.android.internal.util.Preconditions; + +import dalvik.annotation.optimization.CriticalNative; + +import libcore.util.NativeAllocationRegistry; + +/** + * Result of text shaping of the single paragraph string. + * + * <p> + * <pre> + * <code> + * Paint paint = new Paint(); + * Paint bigPaint = new Paint(); + * bigPaint.setTextSize(paint.getTextSize() * 2.0); + * String text = "Hello, Android."; + * MeasuredText mt = new MeasuredText.Builder(text.toCharArray()) + * .appendStyleRun(paint, 7, false) // Use paint for "Hello, " + * .appendStyleRun(bigPaint, 8, false) // Use bigPaint for "Android." + * .build(); + * </code> + * </pre> + * </p> + */ +public class MeasuredText { + private long mNativePtr; + private boolean mComputeHyphenation; + private boolean mComputeLayout; + private @NonNull char[] mChars; + + // Use builder instead. + private MeasuredText(long ptr, @NonNull char[] chars, boolean computeHyphenation, + boolean computeLayout) { + mNativePtr = ptr; + mChars = chars; + mComputeHyphenation = computeHyphenation; + mComputeLayout = computeLayout; + } + + /** + * Returns the characters in the paragraph used to compute this MeasuredText instance. + * @hide + */ + public @NonNull char[] getChars() { + return mChars; + } + + /** + * Returns the width of a given range. + * + * @param start an inclusive start index of the range + * @param end an exclusive end index of the range + */ + public @FloatRange(from = 0.0) @Px float getWidth( + @IntRange(from = 0) int start, @IntRange(from = 0) int end) { + Preconditions.checkArgument(0 <= start && start <= mChars.length, + "start(" + start + ") must be 0 <= start <= " + mChars.length); + Preconditions.checkArgument(0 <= end && end <= mChars.length, + "end(" + end + ") must be 0 <= end <= " + mChars.length); + Preconditions.checkArgument(start <= end, + "start(" + start + ") is larger than end(" + end + ")"); + return nGetWidth(mNativePtr, start, end); + } + + /** + * Returns a memory usage of the native object. + * + * @hide + */ + public int getMemoryUsage() { + return nGetMemoryUsage(mNativePtr); + } + + /** + * Retrieves the boundary box of the given range + * + * @param start an inclusive start index of the range + * @param end an exclusive end index of the range + * @param rect an output parameter + */ + public void getBounds(@IntRange(from = 0) int start, @IntRange(from = 0) int end, + @NonNull Rect rect) { + Preconditions.checkArgument(0 <= start && start <= mChars.length, + "start(" + start + ") must be 0 <= start <= " + mChars.length); + Preconditions.checkArgument(0 <= end && end <= mChars.length, + "end(" + end + ") must be 0 <= end <= " + mChars.length); + Preconditions.checkArgument(start <= end, + "start(" + start + ") is larger than end(" + end + ")"); + Preconditions.checkNotNull(rect); + nGetBounds(mNativePtr, mChars, start, end, rect); + } + + /** + * Returns the width of the character at the given offset. + * + * @param offset an offset of the character. + */ + public @FloatRange(from = 0.0f) @Px float getCharWidthAt(@IntRange(from = 0) int offset) { + Preconditions.checkArgument(0 <= offset && offset < mChars.length, + "offset(" + offset + ") is larger than text length: " + mChars.length); + return nGetCharWidthAt(mNativePtr, offset); + } + + /** + * Returns a native pointer of the underlying native object. + * + * @hide + */ + public long getNativePtr() { + return mNativePtr; + } + + @CriticalNative + private static native float nGetWidth(/* Non Zero */ long nativePtr, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end); + + @CriticalNative + private static native /* Non Zero */ long nGetReleaseFunc(); + + @CriticalNative + private static native int nGetMemoryUsage(/* Non Zero */ long nativePtr); + + private static native void nGetBounds(long nativePtr, char[] buf, int start, int end, + Rect rect); + + @CriticalNative + private static native float nGetCharWidthAt(long nativePtr, int offset); + + /** + * Helper class for creating a {@link MeasuredText}. + * <p> + * <pre> + * <code> + * Paint paint = new Paint(); + * String text = "Hello, Android."; + * MeasuredText mt = new MeasuredText.Builder(text.toCharArray()) + * .appendStyleRun(paint, text.length, false) + * .build(); + * </code> + * </pre> + * </p> + * + * Note: The appendStyle and appendReplacementRun should be called to cover the text length. + */ + public static final class Builder { + private static final NativeAllocationRegistry sRegistry = + NativeAllocationRegistry.createMalloced( + MeasuredText.class.getClassLoader(), nGetReleaseFunc()); + + private long mNativePtr; + + private final @NonNull char[] mText; + private boolean mComputeHyphenation = false; + private boolean mComputeLayout = true; + private int mCurrentOffset = 0; + private @Nullable MeasuredText mHintMt = null; + + /** + * Construct a builder. + * + * The MeasuredText returned by build method will hold a reference of the text. Developer is + * not supposed to modify the text. + * + * @param text a text + */ + public Builder(@NonNull char[] text) { + Preconditions.checkNotNull(text); + mText = text; + mNativePtr = nInitBuilder(); + } + + /** + * Construct a builder with existing MeasuredText. + * + * The MeasuredText returned by build method will hold a reference of the text. Developer is + * not supposed to modify the text. + * + * @param text a text + */ + public Builder(@NonNull MeasuredText text) { + Preconditions.checkNotNull(text); + mText = text.mChars; + mNativePtr = nInitBuilder(); + if (!text.mComputeLayout) { + throw new IllegalArgumentException( + "The input MeasuredText must not be created with setComputeLayout(false)."); + } + mComputeHyphenation = text.mComputeHyphenation; + mComputeLayout = text.mComputeLayout; + mHintMt = text; + } + + /** + * Apply styles to the given length. + * + * Keeps an internal offset which increases at every append. The initial value for this + * offset is zero. After the style is applied the internal offset is moved to {@code offset + * + length}, and next call will start from this new position. + * + * @param paint a paint + * @param length a length to be applied with a given paint, can not exceed the length of the + * text + * @param isRtl true if the text is in RTL context, otherwise false. + */ + public @NonNull Builder appendStyleRun(@NonNull Paint paint, @IntRange(from = 0) int length, + boolean isRtl) { + Preconditions.checkNotNull(paint); + Preconditions.checkArgument(length > 0, "length can not be negative"); + final int end = mCurrentOffset + length; + Preconditions.checkArgument(end <= mText.length, "Style exceeds the text length"); + nAddStyleRun(mNativePtr, paint.getNativeInstance(), mCurrentOffset, end, isRtl); + mCurrentOffset = end; + return this; + } + + /** + * Used to inform the text layout that the given length is replaced with the object of given + * width. + * + * Keeps an internal offset which increases at every append. The initial value for this + * offset is zero. After the style is applied the internal offset is moved to {@code offset + * + length}, and next call will start from this new position. + * + * Informs the layout engine that the given length should not be processed, instead the + * provided width should be used for calculating the width of that range. + * + * @param length a length to be replaced with the object, can not exceed the length of the + * text + * @param width a replacement width of the range + */ + public @NonNull Builder appendReplacementRun(@NonNull Paint paint, + @IntRange(from = 0) int length, @Px @FloatRange(from = 0) float width) { + Preconditions.checkArgument(length > 0, "length can not be negative"); + final int end = mCurrentOffset + length; + Preconditions.checkArgument(end <= mText.length, "Replacement exceeds the text length"); + nAddReplacementRun(mNativePtr, paint.getNativeInstance(), mCurrentOffset, end, width); + mCurrentOffset = end; + return this; + } + + /** + * By passing true to this method, the build method will compute all possible hyphenation + * pieces as well. + * + * If you don't want to use automatic hyphenation, you can pass false to this method and + * save the computation time of hyphenation. The default value is false. + * + * Even if you pass false to this method, you can still enable automatic hyphenation of + * LineBreaker but line break computation becomes slower. + * + * @param computeHyphenation true if you want to use automatic hyphenations. + */ + public @NonNull Builder setComputeHyphenation(boolean computeHyphenation) { + mComputeHyphenation = computeHyphenation; + return this; + } + + /** + * By passing true to this method, the build method will compute all full layout + * information. + * + * If you don't use {@link MeasuredText#getBounds(int,int,android.graphics.Rect)}, you can + * pass false to this method and save the memory spaces. The default value is true. + * + * Even if you pass false to this method, you can still call getBounds but it becomes + * slower. + * + * @param computeLayout true if you want to retrieve full layout info, e.g. bbox. + */ + public @NonNull Builder setComputeLayout(boolean computeLayout) { + mComputeLayout = computeLayout; + return this; + } + + /** + * Creates a MeasuredText. + * + * Once you called build() method, you can't reuse the Builder class again. + * @throws IllegalStateException if this Builder is reused. + * @throws IllegalStateException if the whole text is not covered by one or more runs (style + * or replacement) + */ + public @NonNull MeasuredText build() { + ensureNativePtrNoReuse(); + if (mCurrentOffset != mText.length) { + throw new IllegalStateException("Style info has not been provided for all text."); + } + if (mHintMt != null && mHintMt.mComputeHyphenation != mComputeHyphenation) { + throw new IllegalArgumentException( + "The hyphenation configuration is different from given hint MeasuredText"); + } + try { + long hintPtr = (mHintMt == null) ? 0 : mHintMt.getNativePtr(); + long ptr = nBuildMeasuredText(mNativePtr, hintPtr, mText, mComputeHyphenation, + mComputeLayout); + final MeasuredText res = new MeasuredText(ptr, mText, mComputeHyphenation, + mComputeLayout); + sRegistry.registerNativeAllocation(res, ptr); + return res; + } finally { + nFreeBuilder(mNativePtr); + mNativePtr = 0; + } + } + + /** + * Ensures {@link #mNativePtr} is not reused. + * + * <p/> This is a method by itself to help increase testability - eg. Robolectric might want + * to override the validation behavior in test environment. + */ + private void ensureNativePtrNoReuse() { + if (mNativePtr == 0) { + throw new IllegalStateException("Builder can not be reused."); + } + } + + private static native /* Non Zero */ long nInitBuilder(); + + /** + * Apply style to make native measured text. + * + * @param nativeBuilderPtr The native MeasuredParagraph builder pointer. + * @param paintPtr The native paint pointer to be applied. + * @param start The start offset in the copied buffer. + * @param end The end offset in the copied buffer. + * @param isRtl True if the text is RTL. + */ + private static native void nAddStyleRun(/* Non Zero */ long nativeBuilderPtr, + /* Non Zero */ long paintPtr, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end, + boolean isRtl); + /** + * Apply ReplacementRun to make native measured text. + * + * @param nativeBuilderPtr The native MeasuredParagraph builder pointer. + * @param paintPtr The native paint pointer to be applied. + * @param start The start offset in the copied buffer. + * @param end The end offset in the copied buffer. + * @param width The width of the replacement. + */ + private static native void nAddReplacementRun(/* Non Zero */ long nativeBuilderPtr, + /* Non Zero */ long paintPtr, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end, + @FloatRange(from = 0) float width); + + private static native long nBuildMeasuredText( + /* Non Zero */ long nativeBuilderPtr, + long hintMtPtr, + @NonNull char[] text, + boolean computeHyphenation, + boolean computeLayout); + + private static native void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr); + } +} diff --git a/graphics/proto/Android.bp b/graphics/proto/Android.bp new file mode 100644 index 000000000000..1d06348fb02f --- /dev/null +++ b/graphics/proto/Android.bp @@ -0,0 +1,11 @@ +java_library_static { + name: "game-driver-protos", + host_supported: true, + proto: { + type: "lite", + }, + srcs: ["game_driver.proto"], + no_framework_libs: true, + jarjar_rules: "jarjar-rules.txt", + sdk_version: "28", +} diff --git a/graphics/proto/game_driver.proto b/graphics/proto/game_driver.proto new file mode 100644 index 000000000000..fd7ffccac24c --- /dev/null +++ b/graphics/proto/game_driver.proto @@ -0,0 +1,31 @@ +/* + * Copyright 2019 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. + */ + +syntax = "proto2"; + +package android.gamedriver; + +option java_package = "android.gamedriver"; +option java_outer_classname = "GameDriverProto"; + +message Blacklist { + optional int64 version_code = 1; + repeated string package_names = 2; +} + +message Blacklists { + repeated Blacklist blacklists = 1; +} diff --git a/graphics/proto/jarjar-rules.txt b/graphics/proto/jarjar-rules.txt new file mode 100644 index 000000000000..4e4063706352 --- /dev/null +++ b/graphics/proto/jarjar-rules.txt @@ -0,0 +1 @@ +rule com.google.protobuf.** com.android.framework.protobuf.@1 |