From 000c374e2b00e9192c944e61531570654f720d24 Mon Sep 17 00:00:00 2001
From: John Reck
Date: Thu, 17 Feb 2022 17:43:36 -0500
Subject: Add P010 constant for HardwareBuffer
Also fix a typo in ImageFormat's docs
Fixes: 218928677
Test: make
Change-Id: Icc5d32afac113e639a81a11c86bee1698ef12861
---
graphics/java/android/graphics/ImageFormat.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'graphics/java')
diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index 9feb619b34e3..b341a4e27e67 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -428,7 +428,7 @@ public class ImageFormat {
/**
* Private raw camera sensor image format, a single channel image with
- * implementation depedent pixel layout.
+ * implementation dependent pixel layout.
*
* RAW_PRIVATE is a format for unprocessed raw image buffers coming from an
* image sensor. The actual structure of buffers of this format is
--
cgit v1.2.3-59-g8ed1b
From 4ae65dff9f8d164343d46e8a50c6804d0f0fe7ab Mon Sep 17 00:00:00 2001
From: "James.cf Lin"
Date: Tue, 8 Feb 2022 16:11:47 +0800
Subject: Update the text wrapping API.
1) Update the LineBreakConfig class to be immutable.
2) Do not return null in the PrecomputedText.Params#getLineBreaiConfig API
Bug: 216638444
Test: atest TextViewTest; atest MeasuredTextTest; atest PrecomputedTextTest; atest TextViewPrecomputedTextTest; atest StaticLayoutLineBreakingVariantsTest
Change-Id: I07766137ff6639c7d4acaad07dbcf11a2841cdb0
---
core/api/current.txt | 19 +--
core/java/android/text/PrecomputedText.java | 40 ++-----
core/java/android/text/StaticLayout.java | 3 +-
core/java/android/widget/TextView.java | 130 ++++++++++++---------
core/res/res/values/attrs.xml | 4 +-
.../android/graphics/text/LineBreakConfig.java | 106 ++++++++++++-----
6 files changed, 178 insertions(+), 124 deletions(-)
(limited to 'graphics/java')
diff --git a/core/api/current.txt b/core/api/current.txt
index 4d1e280f798f..5f498034b0fc 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -16384,12 +16384,8 @@ package android.graphics.pdf {
package android.graphics.text {
public final class LineBreakConfig {
- ctor public LineBreakConfig();
method public int getLineBreakStyle();
method public int getLineBreakWordStyle();
- method public void set(@NonNull android.graphics.text.LineBreakConfig);
- method public void setLineBreakStyle(int);
- method public void setLineBreakWordStyle(int);
field public static final int LINE_BREAK_STYLE_LOOSE = 1; // 0x1
field public static final int LINE_BREAK_STYLE_NONE = 0; // 0x0
field public static final int LINE_BREAK_STYLE_NORMAL = 2; // 0x2
@@ -16398,6 +16394,13 @@ package android.graphics.text {
field public static final int LINE_BREAK_WORD_STYLE_PHRASE = 1; // 0x1
}
+ public static final class LineBreakConfig.Builder {
+ ctor public LineBreakConfig.Builder();
+ method @NonNull public android.graphics.text.LineBreakConfig build();
+ method @NonNull public android.graphics.text.LineBreakConfig.Builder setLineBreakStyle(int);
+ method @NonNull public android.graphics.text.LineBreakConfig.Builder setLineBreakWordStyle(int);
+ }
+
public class LineBreaker {
method @NonNull public android.graphics.text.LineBreaker.Result computeLineBreaks(@NonNull android.graphics.text.MeasuredText, @NonNull android.graphics.text.LineBreaker.ParagraphConstraints, @IntRange(from=0) int);
field public static final int BREAK_STRATEGY_BALANCED = 2; // 0x2
@@ -45026,7 +45029,7 @@ package android.text {
public static final class PrecomputedText.Params {
method public int getBreakStrategy();
method public int getHyphenationFrequency();
- method @Nullable public android.graphics.text.LineBreakConfig getLineBreakConfig();
+ method @NonNull public android.graphics.text.LineBreakConfig getLineBreakConfig();
method @NonNull public android.text.TextDirectionHeuristic getTextDirection();
method @NonNull public android.text.TextPaint getTextPaint();
}
@@ -57333,7 +57336,8 @@ package android.widget {
method public final android.text.Layout getLayout();
method public float getLetterSpacing();
method public int getLineBounds(int, android.graphics.Rect);
- method @NonNull public android.graphics.text.LineBreakConfig getLineBreakConfig();
+ method public int getLineBreakStyle();
+ method public int getLineBreakWordStyle();
method public int getLineCount();
method public int getLineHeight();
method public float getLineSpacingExtra();
@@ -57461,7 +57465,8 @@ package android.widget {
method public void setKeyListener(android.text.method.KeyListener);
method public void setLastBaselineToBottomHeight(@IntRange(from=0) @Px int);
method public void setLetterSpacing(float);
- method public void setLineBreakConfig(@NonNull android.graphics.text.LineBreakConfig);
+ method public void setLineBreakStyle(int);
+ method public void setLineBreakWordStyle(int);
method public void setLineHeight(@IntRange(from=0) @Px int);
method public void setLineSpacing(float, float);
method public void setLines(int);
diff --git a/core/java/android/text/PrecomputedText.java b/core/java/android/text/PrecomputedText.java
index be66db2a4c05..f38a10f8b647 100644
--- a/core/java/android/text/PrecomputedText.java
+++ b/core/java/android/text/PrecomputedText.java
@@ -98,7 +98,7 @@ public class PrecomputedText implements Spannable {
private final @Layout.HyphenationFrequency int mHyphenationFrequency;
// The line break configuration for calculating text wrapping.
- private final @Nullable LineBreakConfig mLineBreakConfig;
+ private final @NonNull LineBreakConfig mLineBreakConfig;
/**
* A builder for creating {@link Params}.
@@ -118,7 +118,7 @@ public class PrecomputedText implements Spannable {
Layout.HYPHENATION_FREQUENCY_NORMAL;
// The line break configuration for calculating text wrapping.
- private @Nullable LineBreakConfig mLineBreakConfig;
+ private @NonNull LineBreakConfig mLineBreakConfig = LineBreakConfig.NONE;
/**
* Builder constructor.
@@ -211,7 +211,7 @@ public class PrecomputedText implements Spannable {
// For the external developers, use Builder instead.
/** @hide */
public Params(@NonNull TextPaint paint,
- @Nullable LineBreakConfig lineBreakConfig,
+ @NonNull LineBreakConfig lineBreakConfig,
@NonNull TextDirectionHeuristic textDir,
@Layout.BreakStrategy int strategy,
@Layout.HyphenationFrequency int frequency) {
@@ -259,11 +259,12 @@ public class PrecomputedText implements Spannable {
}
/**
- * Return the line break configuration for this text.
+ * Returns the {@link LineBreakConfig} for this text.
*
- * @return the current line break configuration, null if no line break configuration is set.
+ * @return the current line break configuration. The {@link LineBreakConfig} with default
+ * values will be returned if no line break configuration is set.
*/
- public @Nullable LineBreakConfig getLineBreakConfig() {
+ public @NonNull LineBreakConfig getLineBreakConfig() {
return mLineBreakConfig;
}
@@ -296,9 +297,9 @@ public class PrecomputedText implements Spannable {
/** @hide */
public @CheckResultUsableResult int checkResultUsable(@NonNull TextPaint paint,
@NonNull TextDirectionHeuristic textDir, @Layout.BreakStrategy int strategy,
- @Layout.HyphenationFrequency int frequency, @Nullable LineBreakConfig lbConfig) {
+ @Layout.HyphenationFrequency int frequency, @NonNull LineBreakConfig lbConfig) {
if (mBreakStrategy == strategy && mHyphenationFrequency == frequency
- && isLineBreakEquals(mLineBreakConfig, lbConfig)
+ && mLineBreakConfig.equals(lbConfig)
&& mPaint.equalsForTextMeasurement(paint)) {
return mTextDir == textDir ? USABLE : NEED_RECOMPUTE;
} else {
@@ -306,29 +307,6 @@ public class PrecomputedText implements Spannable {
}
}
- /**
- * Check the two LineBreakConfig instances are equal.
- * This method assumes they are equal if one parameter is null and the other parameter has
- * a LineBreakStyle value of LineBreakConfig.LINE_BREAK_STYLE_NONE.
- *
- * @param o1 the first LineBreakConfig instance.
- * @param o2 the second LineBreakConfig instance.
- * @return true if the two LineBreakConfig instances are equal.
- */
- private boolean isLineBreakEquals(LineBreakConfig o1, LineBreakConfig o2) {
- if (Objects.equals(o1, o2)) {
- return true;
- }
- if (o1 == null && (o2 != null
- && o2.getLineBreakStyle() == LineBreakConfig.LINE_BREAK_STYLE_NONE)) {
- return true;
- } else if (o2 == null && (o1 != null
- && o1.getLineBreakStyle() == LineBreakConfig.LINE_BREAK_STYLE_NONE)) {
- return true;
- }
- return false;
- }
-
/**
* Check if the same text layout.
*
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index b10fc37bff2f..d63d66e965c2 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -410,7 +410,8 @@ public class StaticLayout extends Layout {
*
* @param lineBreakConfig the line break configuration for text wrapping.
* @return this builder, useful for chaining.
- * @see android.widget.TextView#setLineBreakConfig
+ * @see android.widget.TextView#setLineBreakStyle
+ * @see android.widget.TextView#setLineBreakWordStyle
*/
@NonNull
public Builder setLineBreakConfig(@NonNull LineBreakConfig lineBreakConfig) {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 3dfb4a5a084a..dbaf0aaccb1a 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -788,7 +788,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private Layout mLayout;
private boolean mLocalesChanged = false;
private int mTextSizeUnit = -1;
- private LineBreakConfig mLineBreakConfig = new LineBreakConfig();
+ private int mLineBreakStyle = DEFAULT_LINE_BREAK_STYLE;
+ private int mLineBreakWordStyle = DEFAULT_LINE_BREAK_WORD_STYLE;
// This is used to reflect the current user preference for changing font weight and making text
// more bold.
@@ -1457,13 +1458,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
break;
case com.android.internal.R.styleable.TextView_lineBreakStyle:
- mLineBreakConfig.setLineBreakStyle(
- a.getInt(attr, LineBreakConfig.LINE_BREAK_STYLE_NONE));
+ mLineBreakStyle = a.getInt(attr, LineBreakConfig.LINE_BREAK_STYLE_NONE);
break;
case com.android.internal.R.styleable.TextView_lineBreakWordStyle:
- mLineBreakConfig.setLineBreakWordStyle(
- a.getInt(attr, LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE));
+ mLineBreakWordStyle = a.getInt(attr,
+ LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE);
break;
case com.android.internal.R.styleable.TextView_autoSizeTextType:
@@ -4301,13 +4301,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@LineBreakConfig.LineBreakStyle int lineBreakStyle,
@LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) {
boolean updated = false;
- if (isLineBreakStyleSpecified && mLineBreakConfig.getLineBreakStyle() != lineBreakStyle) {
- mLineBreakConfig.setLineBreakStyle(lineBreakStyle);
+ if (isLineBreakStyleSpecified && mLineBreakStyle != lineBreakStyle) {
+ mLineBreakStyle = lineBreakStyle;
updated = true;
}
- if (isLineBreakWordStyleSpecified
- && mLineBreakConfig.getLineBreakWordStyle() != lineBreakWordStyle) {
- mLineBreakConfig.setLineBreakWordStyle(lineBreakWordStyle);
+ if (isLineBreakWordStyleSpecified && mLineBreakWordStyle != lineBreakWordStyle) {
+ mLineBreakWordStyle = lineBreakWordStyle;
updated = true;
}
if (updated && mLayout != null) {
@@ -4871,50 +4870,72 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
- * Sets line break configuration indicates which strategy needs to be used when calculating the
- * text wrapping.
- *
- * There are two types of line break rules that can be configured at the same time. One is
- * line break style(lb) and the other is line break word style(lw). The line break style
- * affects rule-based breaking. The line break word style affects dictionary-based breaking
- * and provide phrase-based breaking opportunities. There are several types for the
- * line break style:
+ * Set the line break style for text wrapping.
+ *
+ * The line break style to indicates the line break strategies can be used when
+ * calculating the text wrapping. The line break style affects rule-based breaking. It
+ * specifies the strictness of line-breaking rules.
+ * There are several types for the line break style:
* {@link LineBreakConfig#LINE_BREAK_STYLE_LOOSE},
* {@link LineBreakConfig#LINE_BREAK_STYLE_NORMAL} and
- * {@link LineBreakConfig#LINE_BREAK_STYLE_STRICT}.
- * The type for the line break word style is
- * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_PHRASE}.
- * The default values of the line break style and the line break word style are
- * {@link LineBreakConfig#LINE_BREAK_STYLE_NONE} and
- * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_NONE} respectively, indicating that no line
- * breaking rules are specified.
- * See
+ * {@link LineBreakConfig#LINE_BREAK_STYLE_STRICT}. The default values of the line break style
+ * is {@link LineBreakConfig#LINE_BREAK_STYLE_NONE}, indicating no breaking rule is specified.
+ * See
* the line-break property
*
- * @param lineBreakConfig the line break config for text wrapping.
+ * @param lineBreakStyle the line break style for the text.
*/
- public void setLineBreakConfig(@NonNull LineBreakConfig lineBreakConfig) {
- Objects.requireNonNull(lineBreakConfig);
- if (mLineBreakConfig.equals(lineBreakConfig)) {
- return;
+ public void setLineBreakStyle(@LineBreakConfig.LineBreakStyle int lineBreakStyle) {
+ if (mLineBreakStyle != lineBreakStyle) {
+ mLineBreakStyle = lineBreakStyle;
+ if (mLayout != null) {
+ nullLayouts();
+ requestLayout();
+ invalidate();
+ }
}
- mLineBreakConfig.set(lineBreakConfig);
- if (mLayout != null) {
- nullLayouts();
- requestLayout();
- invalidate();
+ }
+
+ /**
+ * Set the line break word style for text wrapping.
+ *
+ * The line break word style affects dictionary-based breaking and provide phrase-based
+ * breaking opportunities. The type for the line break word style is
+ * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_PHRASE}. The default values of the line break
+ * word style is {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_NONE}, indicating no breaking rule
+ * is specified.
+ * See
+ * the word-break property
+ *
+ * @param lineBreakWordStyle the line break word style for the tet
+ */
+ public void setLineBreakWordStyle(@LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) {
+ if (mLineBreakWordStyle != lineBreakWordStyle) {
+ mLineBreakWordStyle = lineBreakWordStyle;
+ if (mLayout != null) {
+ nullLayouts();
+ requestLayout();
+ invalidate();
+ }
}
}
/**
- * Get the current line break configuration for text wrapping.
+ * Get the current line break style for text wrapping.
*
- * @return the current line break configuration to be used for text wrapping.
+ * @return the current line break style to be used for text wrapping.
*/
- public @NonNull LineBreakConfig getLineBreakConfig() {
- LineBreakConfig lbConfig = new LineBreakConfig();
- lbConfig.set(mLineBreakConfig);
- return lbConfig;
+ public @LineBreakConfig.LineBreakStyle int getLineBreakStyle() {
+ return mLineBreakStyle;
+ }
+
+ /**
+ * Get the current line word break style for text wrapping.
+ *
+ * @return the current line break word style to be used for text wrapping.
+ */
+ public @LineBreakConfig.LineBreakWordStyle int getLineBreakWordStyle() {
+ return mLineBreakWordStyle;
}
/**
@@ -4924,7 +4945,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @see PrecomputedText
*/
public @NonNull PrecomputedText.Params getTextMetricsParams() {
- return new PrecomputedText.Params(new TextPaint(mTextPaint), mLineBreakConfig,
+ return new PrecomputedText.Params(new TextPaint(mTextPaint),
+ LineBreakConfig.getLineBreakConfig(mLineBreakStyle, mLineBreakWordStyle),
getTextDirectionHeuristic(),
mBreakStrategy, mHyphenationFrequency);
}
@@ -4941,13 +4963,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mTextDir = params.getTextDirection();
mBreakStrategy = params.getBreakStrategy();
mHyphenationFrequency = params.getHyphenationFrequency();
- if (params.getLineBreakConfig() != null) {
- mLineBreakConfig.set(params.getLineBreakConfig());
- } else {
- // Set default value if the line break config in the PrecomputedText.Params is null.
- mLineBreakConfig.setLineBreakStyle(DEFAULT_LINE_BREAK_STYLE);
- mLineBreakConfig.setLineBreakWordStyle(DEFAULT_LINE_BREAK_WORD_STYLE);
- }
+ LineBreakConfig lineBreakConfig = params.getLineBreakConfig();
+ mLineBreakStyle = lineBreakConfig.getLineBreakStyle();
+ mLineBreakWordStyle = lineBreakConfig.getLineBreakWordStyle();
if (mLayout != null) {
nullLayouts();
requestLayout();
@@ -6486,7 +6504,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
final @PrecomputedText.Params.CheckResultUsableResult int checkResult =
precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy,
- mHyphenationFrequency, mLineBreakConfig);
+ mHyphenationFrequency, LineBreakConfig.getLineBreakConfig(
+ mLineBreakStyle, mLineBreakWordStyle));
switch (checkResult) {
case PrecomputedText.Params.UNUSABLE:
throw new IllegalArgumentException(
@@ -9383,7 +9402,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
.setHyphenationFrequency(mHyphenationFrequency)
.setJustificationMode(mJustificationMode)
.setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
- .setLineBreakConfig(mLineBreakConfig);
+ .setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
+ mLineBreakStyle, mLineBreakWordStyle));
if (shouldEllipsize) {
builder.setEllipsize(mEllipsize)
.setEllipsizedWidth(ellipsisWidth);
@@ -9498,7 +9518,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
.setHyphenationFrequency(mHyphenationFrequency)
.setJustificationMode(mJustificationMode)
.setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
- .setLineBreakConfig(mLineBreakConfig);
+ .setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
+ mLineBreakStyle, mLineBreakWordStyle));
if (shouldEllipsize) {
builder.setEllipsize(effectiveEllipsize)
.setEllipsizedWidth(ellipsisWidth);
@@ -9866,7 +9887,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
.setJustificationMode(getJustificationMode())
.setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
.setTextDirection(getTextDirectionHeuristic())
- .setLineBreakConfig(mLineBreakConfig);
+ .setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
+ mLineBreakStyle, mLineBreakWordStyle));
final StaticLayout layout = layoutBuilder.build();
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index fca2bd15787d..025027c74abc 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5471,9 +5471,9 @@
-
+
-
+
diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java
index cffdf28dbc27..d083e444e996 100644
--- a/graphics/java/android/graphics/text/LineBreakConfig.java
+++ b/graphics/java/android/graphics/text/LineBreakConfig.java
@@ -26,7 +26,7 @@ import java.util.Objects;
/**
* Indicates the strategies can be used when calculating the text wrapping.
*
- * See the line-break property
+ * See the line-break property
*/
public final class LineBreakConfig {
@@ -78,39 +78,96 @@ public final class LineBreakConfig {
@Retention(RetentionPolicy.SOURCE)
public @interface LineBreakWordStyle {}
- private @LineBreakStyle int mLineBreakStyle = LINE_BREAK_STYLE_NONE;
- private @LineBreakWordStyle int mLineBreakWordStyle = LINE_BREAK_WORD_STYLE_NONE;
-
- public LineBreakConfig() {
+ /**
+ * A builder for creating {@link LineBreakConfig}.
+ */
+ public static final class Builder {
+ // The line break style for the LineBreakConfig.
+ private @LineBreakStyle int mLineBreakStyle = LineBreakConfig.LINE_BREAK_STYLE_NONE;
+
+ // The line break word style for the LineBreakConfig.
+ private @LineBreakWordStyle int mLineBreakWordStyle =
+ LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE;
+
+ /**
+ * Builder constructor with line break parameters.
+ */
+ public Builder() {
+ }
+
+ /**
+ * Set the line break style.
+ *
+ * @param lineBreakStyle the new line break style.
+ * @return this Builder
+ */
+ public @NonNull Builder setLineBreakStyle(@LineBreakStyle int lineBreakStyle) {
+ mLineBreakStyle = lineBreakStyle;
+ return this;
+ }
+
+ /**
+ * Set the line break word style.
+ *
+ * @param lineBreakWordStyle the new line break word style.
+ * @return this Builder
+ */
+ public @NonNull Builder setLineBreakWordStyle(@LineBreakWordStyle int lineBreakWordStyle) {
+ mLineBreakWordStyle = lineBreakWordStyle;
+ return this;
+ }
+
+ /**
+ * Build the {@link LineBreakConfig}
+ *
+ * @return the LineBreakConfig instance.
+ */
+ public @NonNull LineBreakConfig build() {
+ return new LineBreakConfig(mLineBreakStyle, mLineBreakWordStyle);
+ }
}
/**
- * Set the line break configuration.
+ * Create the LineBreakConfig instance.
*
- * @param lineBreakConfig the new line break configuration.
+ * @param lineBreakStyle the line break style for text wrapping.
+ * @param lineBreakWordStyle the line break word style for text wrapping.
+ * @return the {@link LineBreakConfig} instance.
+ * @hide
*/
- public void set(@NonNull LineBreakConfig lineBreakConfig) {
- Objects.requireNonNull(lineBreakConfig);
- mLineBreakStyle = lineBreakConfig.getLineBreakStyle();
- mLineBreakWordStyle = lineBreakConfig.getLineBreakWordStyle();
+ public static @NonNull LineBreakConfig getLineBreakConfig(@LineBreakStyle int lineBreakStyle,
+ @LineBreakWordStyle int lineBreakWordStyle) {
+ LineBreakConfig.Builder builder = new LineBreakConfig.Builder();
+ return builder.setLineBreakStyle(lineBreakStyle)
+ .setLineBreakWordStyle(lineBreakWordStyle)
+ .build();
}
+ /** @hide */
+ public static final LineBreakConfig NONE =
+ new Builder().setLineBreakStyle(LINE_BREAK_STYLE_NONE)
+ .setLineBreakWordStyle(LINE_BREAK_WORD_STYLE_NONE).build();
+
+ private final @LineBreakStyle int mLineBreakStyle;
+ private final @LineBreakWordStyle int mLineBreakWordStyle;
+
/**
- * Get the line break style.
- *
- * @return The current line break style to be used for the text wrapping.
+ * Constructor with the line break parameters.
+ * Use the {@link LineBreakConfig.Builder} to create the LineBreakConfig instance.
*/
- public @LineBreakStyle int getLineBreakStyle() {
- return mLineBreakStyle;
+ private LineBreakConfig(@LineBreakStyle int lineBreakStyle,
+ @LineBreakWordStyle int lineBreakWordStyle) {
+ mLineBreakStyle = lineBreakStyle;
+ mLineBreakWordStyle = lineBreakWordStyle;
}
/**
- * Set the line break style.
+ * Get the line break style.
*
- * @param lineBreakStyle the new line break style.
+ * @return The current line break style to be used for the text wrapping.
*/
- public void setLineBreakStyle(@LineBreakStyle int lineBreakStyle) {
- mLineBreakStyle = lineBreakStyle;
+ public @LineBreakStyle int getLineBreakStyle() {
+ return mLineBreakStyle;
}
/**
@@ -122,15 +179,6 @@ public final class LineBreakConfig {
return mLineBreakWordStyle;
}
- /**
- * Set the line break word style.
- *
- * @param lineBreakWordStyle the new line break word style.
- */
- public void setLineBreakWordStyle(@LineBreakWordStyle int lineBreakWordStyle) {
- mLineBreakWordStyle = lineBreakWordStyle;
- }
-
@Override
public boolean equals(Object o) {
if (o == null) return false;
--
cgit v1.2.3-59-g8ed1b
From 97726de9053156dfa8cb71cef9b64eb46603a3f1 Mon Sep 17 00:00:00 2001
From: Sally Qi
Date: Wed, 16 Feb 2022 14:18:28 -0800
Subject: Add conversion between @NamedDataSpace and ColorSpace.Named enums
- Add getDataSpace/getFromDataSpace in ColorSpace class
Bug: 220016464
Test: android.graphics.cts.ColorSpaceTest
Change-Id: Ic909c57045ffb6462a7f6b4768e515924715d09a
---
core/api/current.txt | 2 +
graphics/java/android/graphics/ColorSpace.java | 56 ++++++++++++++++++++++++++
2 files changed, 58 insertions(+)
(limited to 'graphics/java')
diff --git a/core/api/current.txt b/core/api/current.txt
index d9e856a6ddc0..362978589eb4 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -14332,6 +14332,8 @@ package android.graphics {
method @NonNull @Size(min=3) public abstract float[] fromXyz(@NonNull @Size(min=3) float[]);
method @NonNull public static android.graphics.ColorSpace get(@NonNull android.graphics.ColorSpace.Named);
method @IntRange(from=1, to=4) public int getComponentCount();
+ method public int getDataSpace();
+ method @Nullable public static android.graphics.ColorSpace getFromDataSpace(int);
method @IntRange(from=android.graphics.ColorSpace.MIN_ID, to=android.graphics.ColorSpace.MAX_ID) public int getId();
method public abstract float getMaxValue(@IntRange(from=0, to=3) int);
method public abstract float getMinValue(@IntRange(from=0, to=3) int);
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index 2f978fc1fc2d..582488ff8de3 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -22,6 +22,10 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
import android.annotation.SuppressAutoDoc;
+import android.annotation.SuppressLint;
+import android.hardware.DataSpace;
+import android.hardware.DataSpace.NamedDataSpace;
+import android.util.SparseIntArray;
import libcore.util.NativeAllocationRegistry;
@@ -207,6 +211,7 @@ public abstract class ColorSpace {
// See static initialization block next to #get(Named)
private static final ColorSpace[] sNamedColorSpaces = new ColorSpace[Named.values().length];
+ private static final SparseIntArray sDataToColorSpaces = new SparseIntArray();
@NonNull private final String mName;
@NonNull private final Model mModel;
@@ -1388,6 +1393,47 @@ public abstract class ColorSpace {
return sNamedColorSpaces[index];
}
+ /**
+ * Create a {@link ColorSpace} object using a {@link android.hardware.DataSpace DataSpace}
+ * value.
+ *
+ * This function maps from a dataspace to a {@link Named} ColorSpace.
+ * If no {@link Named} ColorSpace object matching the {@code dataSpace} value can be created,
+ * {@code null} will return.
+ *
+ * @param dataSpace The dataspace value
+ * @return the ColorSpace object or {@code null} if no matching colorspace can be found.
+ */
+ @SuppressLint("MethodNameUnits")
+ @Nullable
+ public static ColorSpace getFromDataSpace(@NamedDataSpace int dataSpace) {
+ int index = sDataToColorSpaces.get(dataSpace, -1);
+ if (index != -1) {
+ return ColorSpace.get(index);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Retrieve the {@link android.hardware.DataSpace DataSpace} value from a {@link ColorSpace}
+ * object.
+ *
+ * If this {@link ColorSpace} object has no matching {@code dataSpace} value,
+ * {@link android.hardware.DataSpace#DATASPACE_UNKNOWN DATASPACE_UNKNOWN} will return.
+ *
+ * @return the dataspace value.
+ */
+ @SuppressLint("MethodNameUnits")
+ public @NamedDataSpace int getDataSpace() {
+ int index = sDataToColorSpaces.indexOfValue(getId());
+ if (index != -1) {
+ return sDataToColorSpaces.keyAt(index);
+ } else {
+ return DataSpace.DATASPACE_UNKNOWN;
+ }
+ }
+
/**
* Returns an instance of {@link ColorSpace} identified by the specified
* name. The list of names provided in the {@link Named} enum gives access
@@ -1445,6 +1491,7 @@ public abstract class ColorSpace {
SRGB_TRANSFER_PARAMETERS,
Named.SRGB.ordinal()
);
+ sDataToColorSpaces.put(DataSpace.DATASPACE_SRGB, Named.SRGB.ordinal());
sNamedColorSpaces[Named.LINEAR_SRGB.ordinal()] = new ColorSpace.Rgb(
"sRGB IEC61966-2.1 (Linear)",
SRGB_PRIMARIES,
@@ -1453,6 +1500,7 @@ public abstract class ColorSpace {
0.0f, 1.0f,
Named.LINEAR_SRGB.ordinal()
);
+ sDataToColorSpaces.put(DataSpace.DATASPACE_SRGB_LINEAR, Named.LINEAR_SRGB.ordinal());
sNamedColorSpaces[Named.EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb(
"scRGB-nl IEC 61966-2-2:2003",
SRGB_PRIMARIES,
@@ -1464,6 +1512,7 @@ public abstract class ColorSpace {
SRGB_TRANSFER_PARAMETERS,
Named.EXTENDED_SRGB.ordinal()
);
+ sDataToColorSpaces.put(DataSpace.DATASPACE_SCRGB, Named.EXTENDED_SRGB.ordinal());
sNamedColorSpaces[Named.LINEAR_EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb(
"scRGB IEC 61966-2-2:2003",
SRGB_PRIMARIES,
@@ -1472,6 +1521,8 @@ public abstract class ColorSpace {
-0.5f, 7.499f,
Named.LINEAR_EXTENDED_SRGB.ordinal()
);
+ sDataToColorSpaces.put(
+ DataSpace.DATASPACE_SCRGB_LINEAR, Named.LINEAR_EXTENDED_SRGB.ordinal());
sNamedColorSpaces[Named.BT709.ordinal()] = new ColorSpace.Rgb(
"Rec. ITU-R BT.709-5",
new float[] { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f },
@@ -1480,6 +1531,7 @@ public abstract class ColorSpace {
new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45),
Named.BT709.ordinal()
);
+ sDataToColorSpaces.put(DataSpace.DATASPACE_BT709, Named.BT709.ordinal());
sNamedColorSpaces[Named.BT2020.ordinal()] = new ColorSpace.Rgb(
"Rec. ITU-R BT.2020-1",
new float[] { 0.708f, 0.292f, 0.170f, 0.797f, 0.131f, 0.046f },
@@ -1488,6 +1540,7 @@ public abstract class ColorSpace {
new Rgb.TransferParameters(1 / 1.0993, 0.0993 / 1.0993, 1 / 4.5, 0.08145, 1 / 0.45),
Named.BT2020.ordinal()
);
+ sDataToColorSpaces.put(DataSpace.DATASPACE_BT2020, Named.BT2020.ordinal());
sNamedColorSpaces[Named.DCI_P3.ordinal()] = new ColorSpace.Rgb(
"SMPTE RP 431-2-2007 DCI (P3)",
new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f },
@@ -1496,6 +1549,7 @@ public abstract class ColorSpace {
0.0f, 1.0f,
Named.DCI_P3.ordinal()
);
+ sDataToColorSpaces.put(DataSpace.DATASPACE_DCI_P3, Named.DCI_P3.ordinal());
sNamedColorSpaces[Named.DISPLAY_P3.ordinal()] = new ColorSpace.Rgb(
"Display P3",
new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f },
@@ -1504,6 +1558,7 @@ public abstract class ColorSpace {
SRGB_TRANSFER_PARAMETERS,
Named.DISPLAY_P3.ordinal()
);
+ sDataToColorSpaces.put(DataSpace.DATASPACE_DISPLAY_P3, Named.DISPLAY_P3.ordinal());
sNamedColorSpaces[Named.NTSC_1953.ordinal()] = new ColorSpace.Rgb(
"NTSC (1953)",
NTSC_1953_PRIMARIES,
@@ -1528,6 +1583,7 @@ public abstract class ColorSpace {
0.0f, 1.0f,
Named.ADOBE_RGB.ordinal()
);
+ sDataToColorSpaces.put(DataSpace.DATASPACE_ADOBE_RGB, Named.ADOBE_RGB.ordinal());
sNamedColorSpaces[Named.PRO_PHOTO_RGB.ordinal()] = new ColorSpace.Rgb(
"ROMM RGB ISO 22028-2:2013",
new float[] { 0.7347f, 0.2653f, 0.1596f, 0.8404f, 0.0366f, 0.0001f },
--
cgit v1.2.3-59-g8ed1b
From 0bc60e70dabe1c5c5df682ca196f3b32e226a230 Mon Sep 17 00:00:00 2001
From: Seigo Nonaka
Date: Wed, 2 Mar 2022 15:43:49 -0800
Subject: Add legacy emoji font file for compatibility
Bug: 222362958
Test: Manually verified Flutter Gallary shows emoji.
Test: atest CtsTextTestCases CtsGraphicsTestCases
Test: atest FontListParserTest
Change-Id: If5b5ddfb6d0d46cfb8d27cf66843ae95d4b50e03
---
.../src/android/graphics/FontListParserTest.java | 21 +++++++++++++++++++++
data/fonts/fonts.xml | 3 +++
graphics/java/android/graphics/FontListParser.java | 5 ++++-
tools/fonts/fontchain_linter.py | 7 +++++++
4 files changed, 35 insertions(+), 1 deletion(-)
(limited to 'graphics/java')
diff --git a/core/tests/coretests/src/android/graphics/FontListParserTest.java b/core/tests/coretests/src/android/graphics/FontListParserTest.java
index 701e6194d4ee..479e52aab029 100644
--- a/core/tests/coretests/src/android/graphics/FontListParserTest.java
+++ b/core/tests/coretests/src/android/graphics/FontListParserTest.java
@@ -355,6 +355,27 @@ public final class FontListParserTest {
assertThat(config.getAliases()).isEmpty();
}
+ @Test
+ public void ignore() throws Exception {
+ String xml = ""
+ + ""
+ + " "
+ + " test.ttf"
+ + " "
+ + " "
+ + " emoji_legacy.ttf"
+ + " "
+ + " "
+ + " emoji.ttf"
+ + " "
+ + "";
+ FontConfig config = readFamilies(xml, true /* include non-existing font files */);
+ List families = config.getFontFamilies();
+ assertThat(families.size()).isEqualTo(2); // legacy one should be ignored.
+ assertThat(families.get(1).getFontList().get(0).getFile().getName())
+ .isEqualTo("emoji.ttf");
+ }
+
private FontConfig readFamilies(String xml, boolean allowNonExisting)
throws IOException, XmlPullParserException {
ByteArrayInputStream buffer = new ByteArrayInputStream(
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index e050e1784fac..84e949a18c52 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -1328,6 +1328,9 @@
postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+
+ NotoColorEmojiLegacy.ttf
+
NotoColorEmoji.ttf
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index 96b33259e739..4bb16c6b8186 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -218,6 +218,7 @@ public class FontListParser {
final String name = parser.getAttributeValue(null, "name");
final String lang = parser.getAttributeValue("", "lang");
final String variant = parser.getAttributeValue(null, "variant");
+ final String ignore = parser.getAttributeValue(null, "ignore");
final List fonts = new ArrayList<>();
while (keepReading(parser)) {
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
@@ -240,7 +241,9 @@ public class FontListParser {
intVariant = FontConfig.FontFamily.VARIANT_ELEGANT;
}
}
- if (fonts.isEmpty()) {
+
+ boolean skip = (ignore != null && (ignore.equals("true") || ignore.equals("1")));
+ if (skip || fonts.isEmpty()) {
return null;
}
return new FontConfig.FontFamily(fonts, name, LocaleList.forLanguageTags(lang), intVariant);
diff --git a/tools/fonts/fontchain_linter.py b/tools/fonts/fontchain_linter.py
index 2c2c91825162..0d9ea1b70680 100755
--- a/tools/fonts/fontchain_linter.py
+++ b/tools/fonts/fontchain_linter.py
@@ -243,6 +243,8 @@ def parse_fonts_xml(fonts_xml_path):
name = family.get('name')
variant = family.get('variant')
langs = family.get('lang')
+ ignoreAttr = family.get('ignore')
+
if name:
assert variant is None, (
'No variant expected for LGC font %s.' % name)
@@ -259,6 +261,11 @@ def parse_fonts_xml(fonts_xml_path):
name = family.get('name')
variant = family.get('variant')
langs = family.get('lang')
+ ignoreAttr = family.get('ignore')
+ ignore = ignoreAttr == 'true' or ignoreAttr == '1'
+
+ if ignore:
+ continue
if langs:
langs = langs.split()
--
cgit v1.2.3-59-g8ed1b
From f70429088abf54867a920433e9c25f33f773fd53 Mon Sep 17 00:00:00 2001
From: allenwtsu
Date: Thu, 10 Mar 2022 18:13:13 +0800
Subject: Text Wrapping automation
Bug: 220836284
Test: atest StaticLayoutTest
Change-Id: I2aead872b0b472b3c625e075348e46da768ff3be
---
core/java/android/text/StaticLayout.java | 53 ++++++++++++++++++++++
core/java/android/util/FeatureFlagUtils.java | 5 ++
core/java/android/widget/TextView.java | 39 +++++++++++++---
.../src/android/text/StaticLayoutTest.java | 21 +++++++++
.../android/graphics/text/LineBreakConfig.java | 52 +++++++++++++++++++--
5 files changed, 161 insertions(+), 9 deletions(-)
(limited to 'graphics/java')
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 2f85d2b63840..520ceb2582db 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -25,6 +25,7 @@ import android.graphics.Paint;
import android.graphics.text.LineBreakConfig;
import android.graphics.text.LineBreaker;
import android.os.Build;
+import android.os.SystemProperties;
import android.text.style.LeadingMarginSpan;
import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
import android.text.style.LineHeightSpan;
@@ -32,6 +33,7 @@ import android.text.style.TabStopSpan;
import android.util.Log;
import android.util.Pools.SynchronizedPool;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
@@ -73,6 +75,13 @@ public class StaticLayout extends Layout {
* default values.
*/
public final static class Builder {
+ // The content length threshold to enable LINE_BREAK_WORD_STYLE_PHRASE.
+ private static final int DEFAULT_LINECOUNT_THRESHOLD_FOR_PHRASE = 3;
+
+ // The property of content length threshold to enable LINE_BREAK_WORD_STYLE_PHRASE.
+ private static final String PROPERTY_LINECOUNT_THRESHOLD_FOR_PHRASE =
+ "android.phrase.linecount.threshold";
+
private Builder() {}
/**
@@ -431,11 +440,55 @@ public class StaticLayout extends Layout {
*/
@NonNull
public StaticLayout build() {
+ reviseLineBreakConfig();
StaticLayout result = new StaticLayout(this);
Builder.recycle(this);
return result;
}
+ private void reviseLineBreakConfig() {
+ boolean autoPhraseBreaking = mLineBreakConfig.getAutoPhraseBreaking();
+ int wordStyle = mLineBreakConfig.getLineBreakWordStyle();
+ if (autoPhraseBreaking) {
+ if (wordStyle != LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE) {
+ if (shouldEnablePhraseBreaking()) {
+ mLineBreakConfig = LineBreakConfig.getLineBreakConfig(
+ mLineBreakConfig.getLineBreakStyle(),
+ LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE,
+ mLineBreakConfig.getAutoPhraseBreaking());
+ }
+ }
+ }
+ }
+
+ private boolean shouldEnablePhraseBreaking() {
+ if (TextUtils.isEmpty(mText) || mWidth <= 0) {
+ return false;
+ }
+ int lineLimit = SystemProperties.getInt(
+ PROPERTY_LINECOUNT_THRESHOLD_FOR_PHRASE,
+ DEFAULT_LINECOUNT_THRESHOLD_FOR_PHRASE);
+ double desiredWidth = (double) Layout.getDesiredWidth(mText, mStart,
+ mEnd, mPaint, mTextDir);
+ int lineCount = (int) Math.ceil(desiredWidth / mWidth);
+ if (lineCount > 0 && lineCount <= lineLimit) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Get the line break word style.
+ *
+ * @return The current line break word style.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public int getLineBreakWordStyle() {
+ return mLineBreakConfig.getLineBreakWordStyle();
+ }
+
private CharSequence mText;
private int mStart;
private int mEnd;
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 34e7ea74a2db..faf88f88d308 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -74,6 +74,9 @@ public class FeatureFlagUtils {
public static final String SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE =
"settings_hide_second_layer_page_navigate_up_button_in_two_pane";
+ /** @hide */
+ public static final String SETTINGS_AUTO_TEXT_WRAPPING = "settings_auto_text_wrapping";
+
private static final Map DEFAULT_FLAGS;
static {
@@ -100,6 +103,7 @@ public class FeatureFlagUtils {
DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true");
DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "false");
DEFAULT_FLAGS.put(SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE, "true");
+ DEFAULT_FLAGS.put(SETTINGS_AUTO_TEXT_WRAPPING, "false");
}
private static final Set PERSISTENT_FLAGS;
@@ -110,6 +114,7 @@ public class FeatureFlagUtils {
PERSISTENT_FLAGS.add(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS);
PERSISTENT_FLAGS.add(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME);
PERSISTENT_FLAGS.add(SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE);
+ PERSISTENT_FLAGS.add(SETTINGS_AUTO_TEXT_WRAPPING);
}
/**
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 3c8fcb978fbd..ce8d5e8de7b5 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -144,6 +144,7 @@ import android.text.style.UpdateAppearance;
import android.text.util.Linkify;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
+import android.util.FeatureFlagUtils;
import android.util.IntArray;
import android.util.Log;
import android.util.SparseIntArray;
@@ -791,6 +792,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private int mLineBreakStyle = DEFAULT_LINE_BREAK_STYLE;
private int mLineBreakWordStyle = DEFAULT_LINE_BREAK_WORD_STYLE;
+ // The auto option for LINE_BREAK_WORD_STYLE_PHRASE may not be applied in recycled view due to
+ // one-way flag flipping. This is a tentative limitation during experiment and will not have the
+ // issue once this is finalized to LINE_BREAK_WORD_STYLE_PHRASE_AUTO option.
+ private boolean mUserSpeficiedLineBreakwordStyle = false;
+
// This is used to reflect the current user preference for changing font weight and making text
// more bold.
private int mFontWeightAdjustment;
@@ -1462,6 +1468,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
break;
case com.android.internal.R.styleable.TextView_lineBreakWordStyle:
+ if (a.hasValue(attr)) {
+ mUserSpeficiedLineBreakwordStyle = true;
+ }
mLineBreakWordStyle = a.getInt(attr,
LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE);
break;
@@ -4209,6 +4218,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
break;
case com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle:
attributes.mHasLineBreakWordStyle = true;
+ mUserSpeficiedLineBreakwordStyle = true;
attributes.mLineBreakWordStyle =
appearance.getInt(attr, attributes.mLineBreakWordStyle);
break;
@@ -4910,6 +4920,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @param lineBreakWordStyle the line break word style for the tet
*/
public void setLineBreakWordStyle(@LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) {
+ mUserSpeficiedLineBreakwordStyle = true;
if (mLineBreakWordStyle != lineBreakWordStyle) {
mLineBreakWordStyle = lineBreakWordStyle;
if (mLayout != null) {
@@ -4945,8 +4956,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @see PrecomputedText
*/
public @NonNull PrecomputedText.Params getTextMetricsParams() {
+ final boolean autoPhraseBreaking =
+ !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext,
+ FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING);
return new PrecomputedText.Params(new TextPaint(mTextPaint),
- LineBreakConfig.getLineBreakConfig(mLineBreakStyle, mLineBreakWordStyle),
+ LineBreakConfig.getLineBreakConfig(mLineBreakStyle, mLineBreakWordStyle,
+ autoPhraseBreaking),
getTextDirectionHeuristic(),
mBreakStrategy, mHyphenationFrequency);
}
@@ -4966,6 +4981,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
LineBreakConfig lineBreakConfig = params.getLineBreakConfig();
mLineBreakStyle = lineBreakConfig.getLineBreakStyle();
mLineBreakWordStyle = lineBreakConfig.getLineBreakWordStyle();
+ mUserSpeficiedLineBreakwordStyle = true;
if (mLayout != null) {
nullLayouts();
requestLayout();
@@ -6502,10 +6518,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (mTextDir == null) {
mTextDir = getTextDirectionHeuristic();
}
+ final boolean autoPhraseBreaking =
+ !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext,
+ FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING);
final @PrecomputedText.Params.CheckResultUsableResult int checkResult =
precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy,
mHyphenationFrequency, LineBreakConfig.getLineBreakConfig(
- mLineBreakStyle, mLineBreakWordStyle));
+ mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking));
switch (checkResult) {
case PrecomputedText.Params.UNUSABLE:
throw new IllegalArgumentException(
@@ -9391,6 +9410,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
// TODO: code duplication with makeSingleLayout()
if (mHintLayout == null) {
+ final boolean autoPhraseBreaking =
+ !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext,
+ FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING);
StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
mHint.length(), mTextPaint, hintWidth)
.setAlignment(alignment)
@@ -9403,7 +9425,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
.setJustificationMode(mJustificationMode)
.setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
.setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
- mLineBreakStyle, mLineBreakWordStyle));
+ mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking));
if (shouldEllipsize) {
builder.setEllipsize(mEllipsize)
.setEllipsizedWidth(ellipsisWidth);
@@ -9507,6 +9529,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
if (result == null) {
+ final boolean autoPhraseBreaking =
+ !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext,
+ FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING);
StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
0, mTransformed.length(), mTextPaint, wantWidth)
.setAlignment(alignment)
@@ -9519,7 +9544,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
.setJustificationMode(mJustificationMode)
.setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
.setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
- mLineBreakStyle, mLineBreakWordStyle));
+ mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking));
if (shouldEllipsize) {
builder.setEllipsize(effectiveEllipsize)
.setEllipsizedWidth(ellipsisWidth);
@@ -9877,7 +9902,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain(
text, 0, text.length(), mTempTextPaint, Math.round(availableSpace.right));
-
+ final boolean autoPhraseBreaking =
+ !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext,
+ FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING);
layoutBuilder.setAlignment(getLayoutAlignment())
.setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
.setIncludePad(getIncludeFontPadding())
@@ -9888,7 +9915,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
.setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
.setTextDirection(getTextDirectionHeuristic())
.setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
- mLineBreakStyle, mLineBreakWordStyle));
+ mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking));
final StaticLayout layout = layoutBuilder.build();
diff --git a/core/tests/coretests/src/android/text/StaticLayoutTest.java b/core/tests/coretests/src/android/text/StaticLayoutTest.java
index 0ebf03fab966..925da4968517 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutTest.java
@@ -24,6 +24,7 @@ import static org.junit.Assert.assertTrue;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.FontMetricsInt;
+import android.graphics.text.LineBreakConfig;
import android.os.LocaleList;
import android.platform.test.annotations.Presubmit;
import android.text.Layout.Alignment;
@@ -925,4 +926,24 @@ public class StaticLayoutTest {
assertEquals(0, layout.getHeight(true));
assertEquals(2, layout.getLineCount());
}
+
+ @Test
+ public void testBuilder_autoPhraseBreaking() {
+ {
+ // setAutoPhraseBreaking true
+ LineBreakConfig lineBreakConfig = new LineBreakConfig.Builder()
+ .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_STYLE_NONE)
+ .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)
+ .setAutoPhraseBreaking(true)
+ .build();
+ final String text = "これが正解。";
+ // Obtain.
+ StaticLayout.Builder builder = StaticLayout.Builder.obtain(text, 0,
+ text.length(), mDefaultPaint, DEFAULT_OUTER_WIDTH);
+ builder.setLineBreakConfig(lineBreakConfig);
+ builder.build();
+ assertEquals(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE,
+ builder.getLineBreakWordStyle());
+ }
+ }
}
diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java
index d083e444e996..7ad9aecaf6a3 100644
--- a/graphics/java/android/graphics/text/LineBreakConfig.java
+++ b/graphics/java/android/graphics/text/LineBreakConfig.java
@@ -89,6 +89,11 @@ public final class LineBreakConfig {
private @LineBreakWordStyle int mLineBreakWordStyle =
LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE;
+ // Whether or not enabling phrase breaking automatically.
+ // TODO(b/226012260): Remove this and add LINE_BREAK_WORD_STYLE_PHRASE_AUTO after
+ // the experiment.
+ private boolean mAutoPhraseBreaking = false;
+
/**
* Builder constructor with line break parameters.
*/
@@ -117,13 +122,23 @@ public final class LineBreakConfig {
return this;
}
+ /**
+ * Enable or disable the automation of {@link LINE_BREAK_WORD_STYLE_PHRASE}.
+ *
+ * @hide
+ */
+ public @NonNull Builder setAutoPhraseBreaking(boolean autoPhraseBreaking) {
+ mAutoPhraseBreaking = autoPhraseBreaking;
+ return this;
+ }
+
/**
* Build the {@link LineBreakConfig}
*
* @return the LineBreakConfig instance.
*/
public @NonNull LineBreakConfig build() {
- return new LineBreakConfig(mLineBreakStyle, mLineBreakWordStyle);
+ return new LineBreakConfig(mLineBreakStyle, mLineBreakWordStyle, mAutoPhraseBreaking);
}
}
@@ -143,6 +158,23 @@ public final class LineBreakConfig {
.build();
}
+ /**
+ * Create the LineBreakConfig instance.
+ *
+ * @param lineBreakStyle the line break style for text wrapping.
+ * @param lineBreakWordStyle the line break word style for text wrapping.
+ * @return the {@link LineBreakConfig} instance. *
+ * @hide
+ */
+ public static @NonNull LineBreakConfig getLineBreakConfig(@LineBreakStyle int lineBreakStyle,
+ @LineBreakWordStyle int lineBreakWordStyle, boolean autoPhraseBreaking) {
+ LineBreakConfig.Builder builder = new LineBreakConfig.Builder();
+ return builder.setLineBreakStyle(lineBreakStyle)
+ .setLineBreakWordStyle(lineBreakWordStyle)
+ .setAutoPhraseBreaking(autoPhraseBreaking)
+ .build();
+ }
+
/** @hide */
public static final LineBreakConfig NONE =
new Builder().setLineBreakStyle(LINE_BREAK_STYLE_NONE)
@@ -150,15 +182,17 @@ public final class LineBreakConfig {
private final @LineBreakStyle int mLineBreakStyle;
private final @LineBreakWordStyle int mLineBreakWordStyle;
+ private final boolean mAutoPhraseBreaking;
/**
* Constructor with the line break parameters.
* Use the {@link LineBreakConfig.Builder} to create the LineBreakConfig instance.
*/
private LineBreakConfig(@LineBreakStyle int lineBreakStyle,
- @LineBreakWordStyle int lineBreakWordStyle) {
+ @LineBreakWordStyle int lineBreakWordStyle, boolean autoPhraseBreaking) {
mLineBreakStyle = lineBreakStyle;
mLineBreakWordStyle = lineBreakWordStyle;
+ mAutoPhraseBreaking = autoPhraseBreaking;
}
/**
@@ -179,6 +213,17 @@ public final class LineBreakConfig {
return mLineBreakWordStyle;
}
+ /**
+ * Used to identify if the automation of {@link LINE_BREAK_WORD_STYLE_PHRASE} is enabled.
+ *
+ * @return The result that records whether or not the automation of
+ * {@link LINE_BREAK_WORD_STYLE_PHRASE} is enabled.
+ * @hide
+ */
+ public boolean getAutoPhraseBreaking() {
+ return mAutoPhraseBreaking;
+ }
+
@Override
public boolean equals(Object o) {
if (o == null) return false;
@@ -186,7 +231,8 @@ public final class LineBreakConfig {
if (!(o instanceof LineBreakConfig)) return false;
LineBreakConfig that = (LineBreakConfig) o;
return (mLineBreakStyle == that.mLineBreakStyle)
- && (mLineBreakWordStyle == that.mLineBreakWordStyle);
+ && (mLineBreakWordStyle == that.mLineBreakWordStyle)
+ && (mAutoPhraseBreaking == that.mAutoPhraseBreaking);
}
@Override
--
cgit v1.2.3-59-g8ed1b
From f5733b0bdaa1bb50c1901b76641b715fe5d69f11 Mon Sep 17 00:00:00 2001
From: Michael Hoisie
Date: Thu, 21 Apr 2022 22:15:04 +0000
Subject: Remove unused native method Font$Builder.nGetReleaseNativeFont
The nGetReleaseNativeFont native method has been moved to the enclosing
Font class. JNI registration also does not occur for
Font$Builder.nGetReleaseNativeFont.
Test: m -j framework
Bug: 230032992
Change-Id: Ic987812cabe57c5c964b3cb8036cd7e297c11c36
---
graphics/java/android/graphics/fonts/Font.java | 2 --
1 file changed, 2 deletions(-)
(limited to 'graphics/java')
diff --git a/graphics/java/android/graphics/fonts/Font.java b/graphics/java/android/graphics/fonts/Font.java
index abd0be9c2872..28cc05162cbb 100644
--- a/graphics/java/android/graphics/fonts/Font.java
+++ b/graphics/java/android/graphics/fonts/Font.java
@@ -497,8 +497,6 @@ public final class Font {
private static native long nBuild(
long builderPtr, @NonNull ByteBuffer buffer, @NonNull String filePath,
@NonNull String localeList, int weight, boolean italic, int ttcIndex);
- @CriticalNative
- private static native long nGetReleaseNativeFont();
@FastNative
private static native long nClone(long fontPtr, long builderPtr, int weight,
--
cgit v1.2.3-59-g8ed1b
From 72c111de2c445e929ee1de29fd6727ec4225ef75 Mon Sep 17 00:00:00 2001
From: Louis Chang
Date: Mon, 16 May 2022 10:29:25 +0800
Subject: Reduce RippleDrawable background opacity when no window focus
Lower the background opacity when the window loses focus
while the view still has focus, in order to prevent user
confusion in a multi-window environment.
Bug: 230355625
Test: atest RippleDrawableTest
Change-Id: I59cd7faf35f05a12451f015f8ef2da47077b1bf2
---
core/api/test-current.txt | 5 ++++
.../android/graphics/drawable/RippleDrawable.java | 29 ++++++++++++++++++----
2 files changed, 29 insertions(+), 5 deletions(-)
(limited to 'graphics/java')
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 2fff53791f52..814f15eb2a48 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1042,6 +1042,11 @@ package android.graphics.drawable {
method public android.graphics.Xfermode getXfermode();
}
+ public class RippleDrawable extends android.graphics.drawable.LayerDrawable {
+ method public float getTargetBackgroundOpacity();
+ method public void setBackgroundActive(boolean, boolean, boolean, boolean);
+ }
+
public class ShapeDrawable extends android.graphics.drawable.Drawable {
method public void setXfermode(@Nullable android.graphics.Xfermode);
}
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 74cad1aaa057..54c9f6222f7e 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -16,6 +16,8 @@
package android.graphics.drawable;
+import android.annotation.TestApi;
+
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
import static java.lang.annotation.ElementType.METHOD;
@@ -321,6 +323,7 @@ public class RippleDrawable extends LayerDrawable {
boolean pressed = false;
boolean focused = false;
boolean hovered = false;
+ boolean windowFocused = false;
for (int state : stateSet) {
if (state == R.attr.state_enabled) {
@@ -331,10 +334,12 @@ public class RippleDrawable extends LayerDrawable {
pressed = true;
} else if (state == R.attr.state_hovered) {
hovered = true;
+ } else if (state == R.attr.state_window_focused) {
+ windowFocused = true;
}
}
setRippleActive(enabled && pressed);
- setBackgroundActive(hovered, focused, pressed);
+ setBackgroundActive(hovered, focused, pressed, windowFocused);
return changed;
}
@@ -358,7 +363,10 @@ public class RippleDrawable extends LayerDrawable {
}
}
- private void setBackgroundActive(boolean hovered, boolean focused, boolean pressed) {
+ /** @hide */
+ @TestApi
+ public void setBackgroundActive(boolean hovered, boolean focused, boolean pressed,
+ boolean windowFocused) {
if (mState.mRippleStyle == STYLE_SOLID) {
if (mBackground == null && (hovered || focused)) {
mBackground = new RippleBackground(this, mHotspotBounds, isBounded());
@@ -370,7 +378,7 @@ public class RippleDrawable extends LayerDrawable {
} else {
if (focused || hovered) {
if (!pressed) {
- enterPatternedBackgroundAnimation(focused, hovered);
+ enterPatternedBackgroundAnimation(focused, hovered, windowFocused);
}
} else {
exitPatternedBackgroundAnimation();
@@ -840,9 +848,20 @@ public class RippleDrawable extends LayerDrawable {
invalidateSelf(false);
}
- private void enterPatternedBackgroundAnimation(boolean focused, boolean hovered) {
+ /** @hide */
+ @TestApi
+ public float getTargetBackgroundOpacity() {
+ return mTargetBackgroundOpacity;
+ }
+
+ private void enterPatternedBackgroundAnimation(boolean focused, boolean hovered,
+ boolean windowFocused) {
mBackgroundOpacity = 0;
- mTargetBackgroundOpacity = focused ? .6f : hovered ? .2f : 0f;
+ if (focused) {
+ mTargetBackgroundOpacity = windowFocused ? .6f : .2f;
+ } else {
+ mTargetBackgroundOpacity = hovered ? .2f : 0f;
+ }
if (mBackgroundAnimation != null) mBackgroundAnimation.cancel();
// after cancel
mRunBackgroundAnimation = true;
--
cgit v1.2.3-59-g8ed1b
From 0547bffe400ecc877e22701d6bd7f6af33c29b4d Mon Sep 17 00:00:00 2001
From: Brian Osman
Date: Wed, 29 Jun 2022 20:34:19 +0000
Subject: Fix example AGSL code in the javadoc comments
Bug: b/237562620
Change-Id: I5f562b70d04bc86779190002a2640fb51dc00531
---
graphics/java/android/graphics/RuntimeShader.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'graphics/java')
diff --git a/graphics/java/android/graphics/RuntimeShader.java b/graphics/java/android/graphics/RuntimeShader.java
index 6abe34b1d675..9c36fc36474c 100644
--- a/graphics/java/android/graphics/RuntimeShader.java
+++ b/graphics/java/android/graphics/RuntimeShader.java
@@ -214,7 +214,7 @@ import libcore.util.NativeAllocationRegistry;
* uniform shader myShader;
* vec4 main(vec2 canvas_coordinates) {
* // swap the red and blue color channels when sampling from myShader
- * return myShader.sample(canvas_coordinates).bgra;
+ * return myShader.eval(canvas_coordinates).bgra;
* }
*
* After creating a {@link RuntimeShader} with that program the shader uniform can
--
cgit v1.2.3-59-g8ed1b
From c8abba490763142b12f034a7a8578ab33959794f Mon Sep 17 00:00:00 2001
From: Haoyu Zhang
Date: Thu, 23 Jun 2022 10:29:24 -0700
Subject: Introduce TextLine#measureAllbounds
This new method computes the horizontal character bounds by making
use of the character advances returned from native layer. It'll be
used in TextView to populate character bounds, and it's much efficent
compared to calling getPrimaryHorizontal for each character.
Bug: 233922052
Test: atest android.text.TextLineTest
Change-Id: Icdd53e61e2d1513b2231affb19bb00ea5d938d48
---
core/api/current.txt | 2 +
core/java/android/text/TextLine.java | 200 ++++++++++++---
.../coretests/src/android/text/TextLineTest.java | 268 ++++++++++++++++++++-
graphics/java/android/graphics/Paint.java | 125 ++++++++++
libs/hwui/jni/Paint.cpp | 248 ++++++++++---------
5 files changed, 698 insertions(+), 145 deletions(-)
(limited to 'graphics/java')
diff --git a/core/api/current.txt b/core/api/current.txt
index bb3264701ca5..8d703d048e3b 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -14928,6 +14928,8 @@ package android.graphics {
method public android.graphics.PathEffect getPathEffect();
method public float getRunAdvance(char[], int, int, int, int, boolean, int);
method public float getRunAdvance(CharSequence, int, int, int, int, boolean, int);
+ method public float getRunCharacterAdvance(@NonNull char[], int, int, int, int, boolean, int, @Nullable float[], int);
+ method public float getRunCharacterAdvance(@NonNull CharSequence, int, int, int, int, boolean, int, @Nullable float[], int);
method public android.graphics.Shader getShader();
method @ColorInt public int getShadowLayerColor();
method @ColorLong public long getShadowLayerColorLong();
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index e39231c9d64b..7394c22ee93f 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -408,14 +408,14 @@ public class TextLine {
final boolean sameDirection = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
if (targetIsInThisSegment && sameDirection) {
- return h + measureRun(segStart, offset, j, runIsRtl, fmi);
+ return h + measureRun(segStart, offset, j, runIsRtl, fmi, null, 0);
}
- final float segmentWidth = measureRun(segStart, j, j, runIsRtl, fmi);
+ final float segmentWidth = measureRun(segStart, j, j, runIsRtl, fmi, null, 0);
h += sameDirection ? segmentWidth : -segmentWidth;
if (targetIsInThisSegment) {
- return h + measureRun(segStart, offset, j, runIsRtl, null);
+ return h + measureRun(segStart, offset, j, runIsRtl, null, null, 0);
}
if (j != runLimit) { // charAt(j) == TAB_CHAR
@@ -436,6 +436,116 @@ public class TextLine {
return h;
}
+ /**
+ * Return the signed horizontal bounds of the characters in the line.
+ *
+ * The length of the returned array equals to 2 * mLen. The left bound of the i th character
+ * is stored at index 2 * i. And the right bound of the i th character is stored at index
+ * (2 * i + 1).
+ *
+ * Check the following examples. LX(e.g. L0, L1, ...) denotes a character which has LTR BiDi
+ * property. On the other hand, RX(e.g. R0, R1, ...) denotes a character which has RTL BiDi
+ * property. Assuming all character has 1em width.
+ *
+ * Example 1: All LTR chars within LTR context
+ * Input Text (logical) : L0 L1 L2 L3
+ * Input Text (visual) : L0 L1 L2 L3
+ * Output : [0em, 1em, 1em, 2em, 2em, 3em, 3em, 4em]
+ *
+ * Example 2: All RTL chars within RTL context.
+ * Input Text (logical) : R0 R1 R2 R3
+ * Input Text (visual) : R3 R2 R1 R0
+ * Output : [-1em, 0em, -2em, -1em, -3em, -2em, -4em, -3em]
+
+ *
+ * Example 3: BiDi chars within LTR context.
+ * Input Text (logical) : L0 L1 R2 R3 L4 L5
+ * Input Text (visual) : L0 L1 R3 R2 L4 L5
+ * Output : [0em, 1em, 1em, 2em, 3em, 4em, 2em, 3em, 4em, 5em, 5em, 6em]
+
+ *
+ * Example 4: BiDi chars within RTL context.
+ * Input Text (logical) : L0 L1 R2 R3 L4 L5
+ * Input Text (visual) : L4 L5 R3 R2 L0 L1
+ * Output : [-2em, -1em, -1em, 0em, -3em, -2em, -4em, -3em, -6em, -5em, -5em, -4em]
+ *
+ * @param bounds the array to receive the character bounds data. Its length should be at least
+ * 2 times of the line length.
+ * @param advances the array to receive the character advance data, nullable. If provided, its
+ * length should be equal or larger than the line length.
+ *
+ * @throws IllegalArgumentException if the given {@code bounds} is null.
+ * @throws IndexOutOfBoundsException if the given {@code bounds} or {@code advances} doesn't
+ * have enough space to hold the result.
+ */
+ public void measureAllBounds(@NonNull float[] bounds, @Nullable float[] advances) {
+ if (bounds == null) {
+ throw new IllegalArgumentException("bounds can't be null");
+ }
+ if (bounds.length < 2 * mLen) {
+ throw new IndexOutOfBoundsException("bounds doesn't have enough space to receive the "
+ + "result, needed: " + (2 * mLen) + " had: " + bounds.length);
+ }
+ if (advances == null) {
+ advances = new float[mLen];
+ }
+ if (advances.length < mLen) {
+ throw new IndexOutOfBoundsException("advance doesn't have enough space to receive the "
+ + "result, needed: " + mLen + " had: " + advances.length);
+ }
+ float h = 0;
+ for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) {
+ final int runStart = mDirections.getRunStart(runIndex);
+ if (runStart > mLen) break;
+ final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
+ final boolean runIsRtl = mDirections.isRunRtl(runIndex);
+
+ int segStart = runStart;
+ for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
+ if (j == runLimit || charAt(j) == TAB_CHAR) {
+ final boolean sameDirection = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
+
+ final float segmentWidth =
+ measureRun(segStart, j, j, runIsRtl, null, advances, segStart);
+
+ final float oldh = h;
+ h += sameDirection ? segmentWidth : -segmentWidth;
+ float currh = sameDirection ? oldh : h;
+ for (int offset = segStart; offset < j && offset < mLen; ++offset) {
+ if (runIsRtl) {
+ bounds[2 * offset + 1] = currh;
+ currh -= advances[offset];
+ bounds[2 * offset] = currh;
+ } else {
+ bounds[2 * offset] = currh;
+ currh += advances[offset];
+ bounds[2 * offset + 1] = currh;
+ }
+ }
+
+ if (j != runLimit) { // charAt(j) == TAB_CHAR
+ final float leftX;
+ final float rightX;
+ if (runIsRtl) {
+ rightX = h;
+ h = mDir * nextTab(h * mDir);
+ leftX = h;
+ } else {
+ leftX = h;
+ h = mDir * nextTab(h * mDir);
+ rightX = h;
+ }
+ bounds[2 * j] = leftX;
+ bounds[2 * j + 1] = rightX;
+ advances[j] = rightX - leftX;
+ }
+
+ segStart = j + 1;
+ }
+ }
+ }
+ }
+
/**
* @see #measure(int, boolean, FontMetricsInt)
* @return The measure results for all possible offsets
@@ -464,15 +574,15 @@ public class TextLine {
if (j == runLimit || charAt(j) == TAB_CHAR) {
final float oldh = h;
final boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
- final float w = measureRun(segStart, j, j, runIsRtl, fmi);
+ final float w = measureRun(segStart, j, j, runIsRtl, fmi, null, 0);
h += advance ? w : -w;
final float baseh = advance ? oldh : h;
FontMetricsInt crtfmi = advance ? fmi : null;
for (int offset = segStart; offset <= j && offset <= mLen; ++offset) {
if (target[offset] >= segStart && target[offset] < j) {
- measurement[offset] =
- baseh + measureRun(segStart, offset, j, runIsRtl, crtfmi);
+ measurement[offset] = baseh
+ + measureRun(segStart, offset, j, runIsRtl, crtfmi, null, 0);
}
}
@@ -518,14 +628,14 @@ public class TextLine {
boolean needWidth) {
if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
- float w = -measureRun(start, limit, limit, runIsRtl, null);
+ float w = -measureRun(start, limit, limit, runIsRtl, null, null, 0);
handleRun(start, limit, limit, runIsRtl, c, null, x + w, top,
- y, bottom, null, false);
+ y, bottom, null, false, null, 0);
return w;
}
return handleRun(start, limit, limit, runIsRtl, c, null, x, top,
- y, bottom, null, needWidth);
+ y, bottom, null, needWidth, null, 0);
}
/**
@@ -538,12 +648,15 @@ public class TextLine {
* @param runIsRtl true if the run is right-to-left
* @param fmi receives metrics information about the requested
* run, can be null.
+ * @param advances receives the advance information about the requested run, can be null.
+ * @param advancesIndex the start index to fill in the advance information.
* @return the signed width from the start of the run to the leading edge
* of the character at offset, based on the run (not paragraph) direction
*/
private float measureRun(int start, int offset, int limit, boolean runIsRtl,
- FontMetricsInt fmi) {
- return handleRun(start, offset, limit, runIsRtl, null, null, 0, 0, 0, 0, fmi, true);
+ @Nullable FontMetricsInt fmi, @Nullable float[] advances, int advancesIndex) {
+ return handleRun(start, offset, limit, runIsRtl, null, null, 0, 0, 0, 0, fmi, true,
+ advances, advancesIndex);
}
/**
@@ -562,13 +675,14 @@ public class TextLine {
int limit, boolean runIsRtl, float x, boolean needWidth) {
if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
- float w = -measureRun(start, limit, limit, runIsRtl, null);
- handleRun(start, limit, limit, runIsRtl, null, consumer, x + w, 0, 0, 0, null, false);
+ float w = -measureRun(start, limit, limit, runIsRtl, null, null, 0);
+ handleRun(start, limit, limit, runIsRtl, null, consumer, x + w, 0, 0, 0, null,
+ false, null, 0);
return w;
}
return handleRun(start, limit, limit, runIsRtl, null, consumer, x, 0, 0, 0, null,
- needWidth);
+ needWidth, null, 0);
}
@@ -908,14 +1022,16 @@ public class TextLine {
}
private float getRunAdvance(TextPaint wp, int start, int end, int contextStart, int contextEnd,
- boolean runIsRtl, int offset) {
+ boolean runIsRtl, int offset, @Nullable float[] advances, int advancesIndex) {
if (mCharsValid) {
- return wp.getRunAdvance(mChars, start, end, contextStart, contextEnd, runIsRtl, offset);
+ return wp.getRunCharacterAdvance(mChars, start, end, contextStart, contextEnd,
+ runIsRtl, offset, advances, advancesIndex);
} else {
final int delta = mStart;
- if (mComputed == null) {
- return wp.getRunAdvance(mText, delta + start, delta + end,
- delta + contextStart, delta + contextEnd, runIsRtl, delta + offset);
+ if (mComputed == null || advances != null) {
+ return wp.getRunCharacterAdvance(mText, delta + start, delta + end,
+ delta + contextStart, delta + contextEnd, runIsRtl,
+ delta + offset, advances, advancesIndex);
} else {
return mComputed.getWidth(start + delta, end + delta);
}
@@ -940,6 +1056,8 @@ public class TextLine {
* @param needWidth true if the width of the run is needed
* @param offset the offset for the purpose of measuring
* @param decorations the list of locations and paremeters for drawing decorations
+ * @param advances receives the advance information about the requested run, can be null.
+ * @param advancesIndex the start index to fill in the advance information.
* @return the signed width of the run based on the run direction; only
* valid if needWidth is true
*/
@@ -947,7 +1065,8 @@ public class TextLine {
int contextStart, int contextEnd, boolean runIsRtl,
Canvas c, TextShaper.GlyphsConsumer consumer, float x, int top, int y, int bottom,
FontMetricsInt fmi, boolean needWidth, int offset,
- @Nullable ArrayList decorations) {
+ @Nullable ArrayList decorations,
+ @Nullable float[] advances, int advancesIndex) {
if (mIsJustifying) {
wp.setWordSpacing(mAddedWidthForJustify);
@@ -967,7 +1086,8 @@ public class TextLine {
final int numDecorations = decorations == null ? 0 : decorations.size();
if (needWidth || ((c != null || consumer != null) && (wp.bgColor != 0
|| numDecorations != 0 || runIsRtl))) {
- totalWidth = getRunAdvance(wp, start, end, contextStart, contextEnd, runIsRtl, offset);
+ totalWidth = getRunAdvance(wp, start, end, contextStart, contextEnd, runIsRtl, offset,
+ advances, advancesIndex);
}
final float leftX, rightX;
@@ -1009,10 +1129,10 @@ public class TextLine {
final int decorationStart = Math.max(info.start, start);
final int decorationEnd = Math.min(info.end, offset);
- float decorationStartAdvance = getRunAdvance(
- wp, start, end, contextStart, contextEnd, runIsRtl, decorationStart);
- float decorationEndAdvance = getRunAdvance(
- wp, start, end, contextStart, contextEnd, runIsRtl, decorationEnd);
+ float decorationStartAdvance = getRunAdvance(wp, start, end, contextStart,
+ contextEnd, runIsRtl, decorationStart, null, 0);
+ float decorationEndAdvance = getRunAdvance(wp, start, end, contextStart,
+ contextEnd, runIsRtl, decorationEnd, null, 0);
final float decorationXLeft, decorationXRight;
if (runIsRtl) {
decorationXLeft = rightX - decorationEndAdvance;
@@ -1179,19 +1299,27 @@ public class TextLine {
* @param bottom the bottom of the line
* @param fmi receives metrics information, can be null
* @param needWidth true if the width is required
+ * @param advances receives the advance information about the requested run, can be null.
+ * @param advancesIndex the start index to fill in the advance information.
* @return the signed width of the run based on the run direction; only
* valid if needWidth is true
*/
private float handleRun(int start, int measureLimit,
int limit, boolean runIsRtl, Canvas c,
TextShaper.GlyphsConsumer consumer, float x, int top, int y,
- int bottom, FontMetricsInt fmi, boolean needWidth) {
+ int bottom, FontMetricsInt fmi, boolean needWidth,
+ @Nullable float[] advances, int advancesIndex) {
if (measureLimit < start || measureLimit > limit) {
throw new IndexOutOfBoundsException("measureLimit (" + measureLimit + ") is out of "
+ "start (" + start + ") and limit (" + limit + ") bounds");
}
+ if (advances != null && advances.length - advancesIndex < measureLimit - start) {
+ throw new IndexOutOfBoundsException("advances doesn't have enough space to receive the "
+ + "result");
+ }
+
// Case of an empty line, make sure we update fmi according to mPaint
if (start == measureLimit) {
final TextPaint wp = mWorkPaint;
@@ -1218,7 +1346,7 @@ public class TextLine {
wp.setStartHyphenEdit(adjustStartHyphenEdit(start, wp.getStartHyphenEdit()));
wp.setEndHyphenEdit(adjustEndHyphenEdit(limit, wp.getEndHyphenEdit()));
return handleText(wp, start, limit, start, limit, runIsRtl, c, consumer, x, top,
- y, bottom, fmi, needWidth, measureLimit, null);
+ y, bottom, fmi, needWidth, measureLimit, null, advances, advancesIndex);
}
// Shaping needs to take into account context up to metric boundaries,
@@ -1257,8 +1385,16 @@ public class TextLine {
}
if (replacement != null) {
- x += handleReplacement(replacement, wp, i, mlimit, runIsRtl, c, x, top, y,
- bottom, fmi, needWidth || mlimit < measureLimit);
+ final float width = handleReplacement(replacement, wp, i, mlimit, runIsRtl, c,
+ x, top, y, bottom, fmi, needWidth || mlimit < measureLimit);
+ x += width;
+ if (advances != null) {
+ // For replacement, the entire width is assigned to the first character.
+ advances[advancesIndex + i - start] = runIsRtl ? -width : width;
+ for (int j = i + 1; j < mlimit; ++j) {
+ advances[advancesIndex + j - start] = 0.0f;
+ }
+ }
continue;
}
@@ -1300,7 +1436,8 @@ public class TextLine {
adjustEndHyphenEdit(activeEnd, mPaint.getEndHyphenEdit()));
x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c,
consumer, x, top, y, bottom, fmi, needWidth || activeEnd < measureLimit,
- Math.min(activeEnd, mlimit), mDecorations);
+ Math.min(activeEnd, mlimit), mDecorations,
+ advances, advancesIndex + activeStart - start);
activeStart = j;
activePaint.set(wp);
@@ -1327,7 +1464,8 @@ public class TextLine {
adjustEndHyphenEdit(activeEnd, mPaint.getEndHyphenEdit()));
x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, consumer, x,
top, y, bottom, fmi, needWidth || activeEnd < measureLimit,
- Math.min(activeEnd, mlimit), mDecorations);
+ Math.min(activeEnd, mlimit), mDecorations,
+ advances, advancesIndex + activeStart - start);
}
return x - originalX;
diff --git a/core/tests/coretests/src/android/text/TextLineTest.java b/core/tests/coretests/src/android/text/TextLineTest.java
index 412d6ec975ac..e3bcc8d2c200 100644
--- a/core/tests/coretests/src/android/text/TextLineTest.java
+++ b/core/tests/coretests/src/android/text/TextLineTest.java
@@ -26,6 +26,7 @@ import android.graphics.Paint;
import android.graphics.Typeface;
import android.platform.test.annotations.Presubmit;
import android.text.Layout.TabStops;
+import android.text.style.AbsoluteSizeSpan;
import android.text.style.ReplacementSpan;
import android.text.style.TabStopSpan;
@@ -97,7 +98,7 @@ public class TextLineTest {
InstrumentationRegistry.getInstrumentation().getTargetContext().getAssets(),
"fonts/StaticLayoutLineBreakingTestFont.ttf");
- private TextLine getTextLine(String str, TextPaint paint, TabStops tabStops) {
+ private TextLine getTextLine(CharSequence str, TextPaint paint, TabStops tabStops) {
Layout layout =
StaticLayout.Builder.obtain(str, 0, str.length(), paint, Integer.MAX_VALUE)
.build();
@@ -109,7 +110,7 @@ public class TextLineTest {
return tl;
}
- private TextLine getTextLine(String str, TextPaint paint) {
+ private TextLine getTextLine(CharSequence str, TextPaint paint) {
return getTextLine(str, paint, null);
}
@@ -316,14 +317,275 @@ public class TextLineTest {
assertTrue(span.mIsUsed);
}
+ @Test
+ public void testMeasureAllBounds_LTR() {
+ final TextPaint paint = new TextPaint();
+ paint.setTypeface(TYPEFACE);
+ paint.setTextSize(10.0f); // make 1em = 10px
+
+ TextLine tl = getTextLine("IIIIIV", paint);
+ float[] bounds = new float[12];
+ float[] advances = new float[6];
+ tl.measureAllBounds(bounds, advances);
+ assertArrayEquals(new float[] {0.0f, 10.0f, 10.0f, 20.0f, 20.0f, 30.0f, 30.0f, 40.0f,
+ 40.0f, 50.0f, 50.0f, 100.0f}, bounds, 0.0f);
+ assertArrayEquals(new float[] {10.0f, 10.0f, 10.0f, 10.0f, 10.0f, 50.0f}, advances, 0.0f);
+ }
+
+ @Test
+ public void testMeasureAllBounds_LTR_StyledText() {
+ final TextPaint paint = new TextPaint();
+ paint.setTypeface(TYPEFACE);
+ paint.setTextSize(10.0f); // make 1em = 10px
+ SpannableString text = new SpannableString("IIIIIV");
+ text.setSpan(new AbsoluteSizeSpan(5), 1, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ TextLine tl = getTextLine(text, paint);
+ float[] bounds = new float[12];
+ float[] advances = new float[6];
+ tl.measureAllBounds(bounds, advances);
+ assertArrayEquals(new float[] {0.0f, 10.0f, 10.0f, 15.0f, 15.0f, 20.0f, 20.0f, 30.0f,
+ 30.0f, 40.0f, 40.0f, 90.0f}, bounds, 0.0f);
+ assertArrayEquals(new float[] {10.0f, 5.0f, 5.0f, 10.0f, 10.0f, 50.0f}, advances, 0.0f);
+ }
+
+ @Test
+ public void testMeasureAllBounds_RTL() {
+ final TextPaint paint = new TextPaint();
+ paint.setTypeface(TYPEFACE);
+ paint.setTextSize(10.0f); // make 1em = 10px
+
+ TextLine tl = getTextLine("\u05D0\u05D0\u05D0\u05D0\u05D0\u05D1", paint);
+ float[] bounds = new float[12];
+ float[] advances = new float[6];
+ tl.measureAllBounds(bounds, advances);
+ assertArrayEquals(new float[] {-10.0f, 0.0f, -20.0f, -10.0f, -30.0f, -20.0f, -40.0f, -30.0f,
+ -50.0f, -40.0f, -100.0f, -50.0f}, bounds, 0.0f);
+ assertArrayEquals(new float[] {10.0f, 10.0f, 10.0f, 10.0f, 10.0f, 50.0f}, advances, 0.0f);
+ }
+
+
+ @Test
+ public void testMeasureAllBounds_RTL_StyledText() {
+ final TextPaint paint = new TextPaint();
+ paint.setTypeface(TYPEFACE);
+ paint.setTextSize(10.0f); // make 1em = 10px
+ SpannableString text = new SpannableString("\u05D0\u05D0\u05D0\u05D0\u05D0\u05D1");
+ text.setSpan(new AbsoluteSizeSpan(5), 1, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ TextLine tl = getTextLine(text, paint);
+ float[] bounds = new float[12];
+ float[] advances = new float[6];
+ tl.measureAllBounds(bounds, advances);
+ assertArrayEquals(new float[] {-10.0f, 0.0f, -15.0f, -10.0f, -20.0f, -15.0f,
+ -30.0f, -20.0f, -40.0f, -30.0f, -90.0f, -40.0f}, bounds, 0.0f);
+ assertArrayEquals(new float[] {10.0f, 5.0f, 5.0f, 10.0f, 10.0f, 50.0f}, advances, 0.0f);
+ }
+
+ @Test
+ public void testMeasureAllBounds_BiDi() {
+ final TextPaint paint = new TextPaint();
+ paint.setTypeface(TYPEFACE);
+ paint.setTextSize(10.0f); // make 1em = 10px
+
+ TextLine tl = getTextLine("II\u05D0\u05D0II", paint);
+ float[] bounds = new float[12];
+ float[] advances = new float[6];
+ tl.measureAllBounds(bounds, advances);
+ assertArrayEquals(new float[] {0.0f, 10.0f, 10.0f, 20.0f, 30.0f, 40.0f, 20.0f, 30.0f,
+ 40.0f, 50.0f, 50.0f, 60.0f}, bounds, 0.0f);
+ assertArrayEquals(new float[] {10.0f, 10.0f, 10.0f, 10.0f, 10.0f, 10.0f}, advances, 0.0f);
+ }
+
+ @Test
+ public void testMeasureAllBounds_BiDi2() {
+ final TextPaint paint = new TextPaint();
+ paint.setTypeface(TYPEFACE);
+ paint.setTextSize(10.0f); // make 1em = 10px
+
+ TextLine tl = getTextLine("I" + RLI + "I\u05D0\u05D0" + PDI + "I", paint);
+ float[] bounds = new float[14];
+ float[] advances = new float[7];
+ tl.measureAllBounds(bounds, advances);
+ assertArrayEquals(new float[] {0.0f, 10.0f, 10.0f, 10.0f, 30.0f, 40.0f, 20.0f, 30.0f,
+ 10.0f, 20.0f, 40.0f, 40.0f, 40.0f, 50.0f}, bounds, 0.0f);
+ assertArrayEquals(new float[] {10.0f, 0.0f, 10.0f, 10.0f, 10.0f, 0.0f, 10.0f}, advances,
+ 0.0f);
+ }
+
+ @Test
+ public void testMeasureAllBounds_BiDi3() {
+ final TextPaint paint = new TextPaint();
+ paint.setTypeface(TYPEFACE);
+ paint.setTextSize(10.0f); // make 1em = 10px
+
+ TextLine tl = getTextLine("\u05D0" + LRI + "\u05D0II" + PDI + "\u05D0", paint);
+ float[] bounds = new float[14];
+ float[] advances = new float[7];
+ tl.measureAllBounds(bounds, advances);
+ assertArrayEquals(new float[] {-10.0f, 0.0f, -10.0f, -10.0f, -40.0f, -30.0f,
+ -30.0f, -20.0f, -20.0f, -10.0f, -40.0f, -40.0f, -50.0f, -40.0f}, bounds, 0.0f);
+ assertArrayEquals(new float[] {10.0f, 0.0f, 10.0f, 10.0f, 10.0f, 0.0f, 10.0f}, advances,
+ 0.0f);
+ }
+
+ @Test
+ public void testMeasureAllBounds_styled_BiDi() {
+ final TextPaint paint = new TextPaint();
+ paint.setTypeface(TYPEFACE);
+ paint.setTextSize(10.0f); // make 1em = 10px
+
+ SpannableString text = new SpannableString("II\u05D0\u05D0II");
+ text.setSpan(new AbsoluteSizeSpan(5), 1, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ TextLine tl = getTextLine(text, paint);
+ float[] bounds = new float[12];
+ float[] advances = new float[6];
+ tl.measureAllBounds(bounds, advances);
+ assertArrayEquals(new float[] {0.0f, 10.0f, 10.0f, 15.0f, 25.0f, 30.0f,
+ 15.0f, 25.0f, 30.0f, 40.0f, 40.0f, 50.0f}, bounds, 0.0f);
+ assertArrayEquals(new float[] {10.0f, 5.0f, 5.0f, 10.0f, 10.0f, 10.0f}, advances, 0.0f);
+ }
+
+ @Test
+ public void testMeasureAllBounds_Tab_LTR() {
+ final Object[] spans = { new TabStopSpan.Standard(100) };
+ final TabStops stops = new TabStops(100, spans);
+ final TextPaint paint = new TextPaint();
+ paint.setTypeface(TYPEFACE);
+ paint.setTextSize(10.0f); // make 1em = 10px
+
+ TextLine tl = getTextLine("II\tII", paint, stops);
+ float[] bounds = new float[10];
+ float[] advances = new float[5];
+ tl.measureAllBounds(bounds, advances);
+ assertArrayEquals(new float[] {0.0f, 10.0f, 10.0f, 20.0f, 20.0f, 100.0f, 100.0f, 110.0f,
+ 110.0f, 120.0f}, bounds, 0.0f);
+ assertArrayEquals(new float[] {10.0f, 10.0f, 80.0f, 10.0f, 10.0f}, advances, 0.0f);
+ }
+
+ @Test
+ public void testMeasureAllBounds_Tab_RTL() {
+ final Object[] spans = { new TabStopSpan.Standard(100) };
+ final TabStops stops = new TabStops(100, spans);
+ final TextPaint paint = new TextPaint();
+ paint.setTypeface(TYPEFACE);
+ paint.setTextSize(10.0f); // make 1em = 10px
+
+ TextLine tl = getTextLine("\u05D0\u05D0\t\u05D0\u05D0", paint, stops);
+ float[] bounds = new float[10];
+ float[] advances = new float[5];
+ tl.measureAllBounds(bounds, advances);
+ assertArrayEquals(new float[] {-10.0f, 0.0f, -20.0f, -10.0f, -100.0f, -20.0f,
+ -110.0f, -100.0f, -120.0f, -110.0f}, bounds, 0.0f);
+ assertArrayEquals(new float[] {10.0f, 10.0f, 80.0f, 10.0f, 10.0f}, advances, 0.0f);
+ }
+
+ @Test
+ public void testMeasureAllBounds_Tab_BiDi() {
+ final Object[] spans = { new TabStopSpan.Standard(100) };
+ final TabStops stops = new TabStops(100, spans);
+ final TextPaint paint = new TextPaint();
+ paint.setTypeface(TYPEFACE);
+ paint.setTextSize(10.0f); // make 1em = 10px
+
+ TextLine tl = getTextLine("I\u05D0\tI\u05D0", paint, stops);
+ float[] bounds = new float[10];
+ float[] advances = new float[5];
+ tl.measureAllBounds(bounds, advances);
+ assertArrayEquals(new float[] {0.0f, 10.0f, 10.0f, 20.0f, 20.0f, 100.0f,
+ 100.0f, 110.0f, 110.0f, 120.0f}, bounds, 0.0f);
+ assertArrayEquals(new float[] {10.0f, 10.0f, 80.0f, 10.0f, 10.0f}, advances, 0.0f);
+ }
+
+ @Test
+ public void testMeasureAllBounds_Tab_BiDi2() {
+ final Object[] spans = { new TabStopSpan.Standard(100) };
+ final TabStops stops = new TabStops(100, spans);
+ final TextPaint paint = new TextPaint();
+ paint.setTypeface(TYPEFACE);
+ paint.setTextSize(10.0f); // make 1em = 10px
+
+ TextLine tl = getTextLine("\u05D0I\t\u05D0I", paint, stops);
+ float[] bounds = new float[10];
+ float[] advances = new float[5];
+ tl.measureAllBounds(bounds, advances);
+ assertArrayEquals(new float[] {-10.0f, 0.0f, -20.0f, -10.0f, -100.0f, -20.0f,
+ -110.0f, -100.0f, -120.0f, -110.0f}, bounds, 0.0f);
+ assertArrayEquals(new float[] {10.0f, 10.0f, 80.0f, 10.0f, 10.0f}, advances, 0.0f);
+ }
+
+ @Test
+ public void testMeasureAllBounds_replacement_LTR() {
+ final TextPaint paint = new TextPaint();
+ paint.setTypeface(TYPEFACE);
+ paint.setTextSize(10.0f); // make 1em = 10px
+
+ SpannableString text = new SpannableString("IIIII");
+ text.setSpan(new TestReplacementSpan(5), 1, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ TextLine tl = getTextLine(text, paint);
+ float[] bounds = new float[10];
+ float[] advances = new float[5];
+ tl.measureAllBounds(bounds, advances);
+ assertArrayEquals(new float[] {0.0f, 10.0f, 10.0f, 15.0f, 15.0f, 15.0f,
+ 15.0f, 25.0f, 25.0f, 35.0f}, bounds, 0.0f);
+ assertArrayEquals(new float[] {10.0f, 5.0f, 0.0f, 10.0f, 10.0f}, advances, 0.0f);
+ }
+
+ @Test
+ public void testMeasureAllBounds_replacement_RTL() {
+ final TextPaint paint = new TextPaint();
+ paint.setTypeface(TYPEFACE);
+ paint.setTextSize(10.0f); // make 1em = 10px
+
+ SpannableString text = new SpannableString("\u05D0\u05D0\u05D0\u05D0\u05D0");
+ text.setSpan(new TestReplacementSpan(5), 1, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ TextLine tl = getTextLine(text, paint);
+ float[] bounds = new float[10];
+ float[] advances = new float[5];
+ tl.measureAllBounds(bounds, advances);
+ assertArrayEquals(new float[] {-10.0f, 0.0f, -15.0f, -10.0f, -15.0f, -15.0f,
+ -25.0f, -15.0f, -35.0f, -25.0f}, bounds, 0.0f);
+ assertArrayEquals(new float[] {10.0f, 5.0f, 0.0f, 10.0f, 10.0f}, advances, 0.0f);
+ }
+
+ @Test
+ public void testMeasureAllBounds_replacement_BiDi() {
+ final TextPaint paint = new TextPaint();
+ paint.setTypeface(TYPEFACE);
+ paint.setTextSize(10.0f); // make 1em = 10px
+
+ SpannableString text = new SpannableString("II\u05D0\u05D0II");
+ text.setSpan(new TestReplacementSpan(5), 1, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ TextLine tl = getTextLine(text, paint);
+ float[] bounds = new float[12];
+ float[] advances = new float[6];
+ tl.measureAllBounds(bounds, advances);
+ assertArrayEquals(new float[] {0.0f, 10.0f, 10.0f, 15.0f, 15.0f, 15.0f,
+ 15.0f, 25.0f, 25.0f, 35.0f, 35.0f, 45.0f}, bounds, 0.0f);
+ assertArrayEquals(new float[] {10.0f, 5.0f, 0.0f, 10.0f, 10.0f, 10.0f}, advances, 0.0f);
+ }
+
private static class TestReplacementSpan extends ReplacementSpan {
boolean mIsUsed;
+ private final int mWidth;
+
+ TestReplacementSpan() {
+ mWidth = 0;
+ }
+
+ TestReplacementSpan(int width) {
+ mWidth = width;
+ }
@Override
public int getSize(Paint paint, CharSequence text, int start, int end,
Paint.FontMetricsInt fm) {
mIsUsed = true;
- return 0;
+ return mWidth;
}
@Override
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 451b99ea7550..1a80ab308bb5 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -3136,6 +3136,128 @@ public class Paint {
return result;
}
+
+ /**
+ * Measure the advance of each character within a run of text and also return the cursor
+ * position within the run.
+ *
+ * @see #getRunAdvance(char[], int, int, int, int, boolean, int) for more details.
+ *
+ * @param text the text to measure. Cannot be null.
+ * @param start the index of the start of the range to measure
+ * @param end the index + 1 of the end of the range to measure
+ * @param contextStart the index of the start of the shaping context
+ * @param contextEnd the index + 1 of the end of the shaping context
+ * @param isRtl whether the run is in RTL direction
+ * @param offset index of caret position
+ * @param advances the array that receives the computed character advances
+ * @param advancesIndex the start index from which the advances array is filled
+ * @return width measurement between start and offset
+ * @throws IndexOutOfBoundsException if a) contextStart or contextEnd is out of array's range
+ * or contextStart is larger than contextEnd,
+ * b) start or end is not within the range [contextStart, contextEnd), or start is larger than
+ * end,
+ * c) offset is not within the range [start, end),
+ * d) advances.length - advanceIndex is smaller than the length of the run, which equals to
+ * end - start.
+ *
+ */
+ public float getRunCharacterAdvance(@NonNull char[] text, int start, int end, int contextStart,
+ int contextEnd, boolean isRtl, int offset,
+ @Nullable float[] advances, int advancesIndex) {
+ if (text == null) {
+ throw new IllegalArgumentException("text cannot be null");
+ }
+ if (contextStart < 0 || contextEnd > text.length) {
+ throw new IndexOutOfBoundsException("Invalid Context Range: " + contextStart + ", "
+ + contextEnd + " must be in 0, " + text.length);
+ }
+
+ if (start < contextStart || contextEnd < end) {
+ throw new IndexOutOfBoundsException("Invalid start/end range: " + start + ", " + end
+ + " must be in " + contextStart + ", " + contextEnd);
+ }
+
+ if (offset < start || end < offset) {
+ throw new IndexOutOfBoundsException("Invalid offset position: " + offset
+ + " must be in " + start + ", " + end);
+ }
+
+ if (advances != null && advances.length < advancesIndex - start + end) {
+ throw new IndexOutOfBoundsException("Given array doesn't have enough space to receive "
+ + "the result, advances.length: " + advances.length + " advanceIndex: "
+ + advancesIndex + " needed space: " + (offset - start));
+ }
+
+ if (end == start) {
+ return 0.0f;
+ }
+
+ return nGetRunCharacterAdvance(mNativePaint, text, start, end, contextStart, contextEnd,
+ isRtl, offset, advances, advancesIndex);
+ }
+
+ /**
+ * @see #getRunCharacterAdvance(char[], int, int, int, int, boolean, int, float[], int)
+ *
+ * @param text the text to measure. Cannot be null.
+ * @param start the index of the start of the range to measure
+ * @param end the index + 1 of the end of the range to measure
+ * @param contextStart the index of the start of the shaping context
+ * @param contextEnd the index + 1 of the end of the shaping context
+ * @param isRtl whether the run is in RTL direction
+ * @param offset index of caret position
+ * @param advances the array that receives the computed character advances
+ * @param advancesIndex the start index from which the advances array is filled
+ * @return width measurement between start and offset
+ * @throws IndexOutOfBoundsException if a) contextStart or contextEnd is out of array's range
+ * or contextStart is larger than contextEnd,
+ * b) start or end is not within the range [contextStart, contextEnd), or end is larger than
+ * start,
+ * c) offset is not within the range [start, end),
+ * d) advances.length - advanceIndex is smaller than the run length, which equals to
+ * end - start.
+ */
+ public float getRunCharacterAdvance(@NonNull CharSequence text, int start, int end,
+ int contextStart, int contextEnd, boolean isRtl, int offset,
+ @Nullable float[] advances, int advancesIndex) {
+ if (text == null) {
+ throw new IllegalArgumentException("text cannot be null");
+ }
+ if (contextStart < 0 || contextEnd > text.length()) {
+ throw new IndexOutOfBoundsException("Invalid Context Range: " + contextStart + ", "
+ + contextEnd + " must be in 0, " + text.length());
+ }
+
+ if (start < contextStart || contextEnd < end) {
+ throw new IndexOutOfBoundsException("Invalid start/end range: " + start + ", " + end
+ + " must be in " + contextStart + ", " + contextEnd);
+ }
+
+ if (offset < start || end < offset) {
+ throw new IndexOutOfBoundsException("Invalid offset position: " + offset
+ + " must be in " + start + ", " + end);
+ }
+
+ if (advances != null && advances.length < advancesIndex - start + end) {
+ throw new IndexOutOfBoundsException("Given array doesn't have enough space to receive "
+ + "the result, advances.length: " + advances.length + " advanceIndex: "
+ + advancesIndex + " needed space: " + (offset - start));
+ }
+
+ if (end == start) {
+ return 0.0f;
+ }
+
+ char[] buf = TemporaryBuffer.obtain(contextEnd - contextStart);
+ TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
+ final float result = getRunCharacterAdvance(buf, start - contextStart, end - contextStart,
+ 0, contextEnd - contextStart, isRtl, offset - contextStart,
+ advances, advancesIndex);
+ TemporaryBuffer.recycle(buf);
+ return result;
+ }
+
/**
* Get the character offset within the string whose position is closest to the specified
* horizontal position.
@@ -3247,6 +3369,9 @@ public class Paint {
private static native boolean nHasGlyph(long paintPtr, int bidiFlags, String string);
private static native float nGetRunAdvance(long paintPtr, char[] text, int start, int end,
int contextStart, int contextEnd, boolean isRtl, int offset);
+ private static native float nGetRunCharacterAdvance(long paintPtr, char[] text, int start,
+ int end, int contextStart, int contextEnd, boolean isRtl, int offset, float[] advances,
+ int advancesIndex);
private static native int nGetOffsetForAdvance(long paintPtr, char[] text, int start, int end,
int contextStart, int contextEnd, boolean isRtl, float advance);
private static native void nGetFontMetricsIntForText(long paintPtr, char[] text,
diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp
index 0aa14655725c..ed453b158579 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -497,16 +497,29 @@ namespace PaintGlue {
return true;
}
- static jfloat doRunAdvance(const Paint* paint, const Typeface* typeface, const jchar buf[],
- jint start, jint count, jint bufSize, jboolean isRtl, jint offset) {
+ static jfloat doRunAdvance(JNIEnv* env, const Paint* paint, const Typeface* typeface,
+ const jchar buf[], jint start, jint count, jint bufSize,
+ jboolean isRtl, jint offset, jfloatArray advances,
+ jint advancesIndex) {
+ if (advances) {
+ size_t advancesLength = env->GetArrayLength(advances);
+ if ((size_t)(count + advancesIndex) > advancesLength) {
+ doThrowAIOOBE(env);
+ return 0;
+ }
+ }
minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
- if (offset == start + count) {
+ if (offset == start + count && advances == nullptr) {
return MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count,
bufSize, nullptr);
}
std::unique_ptr advancesArray(new float[count]);
MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count, bufSize,
advancesArray.get());
+
+ if (advances) {
+ env->SetFloatArrayRegion(advances, advancesIndex, count, advancesArray.get());
+ }
return minikin::getRunAdvance(advancesArray.get(), buf, start, count, offset);
}
@@ -515,9 +528,23 @@ namespace PaintGlue {
const Paint* paint = reinterpret_cast(paintHandle);
const Typeface* typeface = paint->getAndroidTypeface();
ScopedCharArrayRO textArray(env, text);
- jfloat result = doRunAdvance(paint, typeface, textArray.get() + contextStart,
- start - contextStart, end - start, contextEnd - contextStart, isRtl,
- offset - contextStart);
+ jfloat result = doRunAdvance(env, paint, typeface, textArray.get() + contextStart,
+ start - contextStart, end - start, contextEnd - contextStart,
+ isRtl, offset - contextStart, nullptr, 0);
+ return result;
+ }
+
+ static jfloat getRunCharacterAdvance___CIIIIZI_FI_F(JNIEnv* env, jclass, jlong paintHandle,
+ jcharArray text, jint start, jint end,
+ jint contextStart, jint contextEnd,
+ jboolean isRtl, jint offset,
+ jfloatArray advances, jint advancesIndex) {
+ const Paint* paint = reinterpret_cast(paintHandle);
+ const Typeface* typeface = paint->getAndroidTypeface();
+ ScopedCharArrayRO textArray(env, text);
+ jfloat result = doRunAdvance(env, paint, typeface, textArray.get() + contextStart,
+ start - contextStart, end - start, contextEnd - contextStart,
+ isRtl, offset - contextStart, advances, advancesIndex);
return result;
}
@@ -1034,113 +1061,112 @@ namespace PaintGlue {
}; // namespace PaintGlue
static const JNINativeMethod methods[] = {
- {"nGetNativeFinalizer", "()J", (void*) PaintGlue::getNativeFinalizer},
- {"nInit","()J", (void*) PaintGlue::init},
- {"nInitWithPaint","(J)J", (void*) PaintGlue::initWithPaint},
- {"nBreakText","(J[CIIFI[F)I", (void*) PaintGlue::breakTextC},
- {"nBreakText","(JLjava/lang/String;ZFI[F)I", (void*) PaintGlue::breakTextS},
- {"nGetTextAdvances","(J[CIIIII[FI)F",
- (void*) PaintGlue::getTextAdvances___CIIIII_FI},
- {"nGetTextAdvances","(JLjava/lang/String;IIIII[FI)F",
- (void*) PaintGlue::getTextAdvances__StringIIIII_FI},
-
- {"nGetTextRunCursor", "(J[CIIIII)I", (void*) PaintGlue::getTextRunCursor___C},
- {"nGetTextRunCursor", "(JLjava/lang/String;IIIII)I",
- (void*) PaintGlue::getTextRunCursor__String},
- {"nGetTextPath", "(JI[CIIFFJ)V", (void*) PaintGlue::getTextPath___C},
- {"nGetTextPath", "(JILjava/lang/String;IIFFJ)V", (void*) PaintGlue::getTextPath__String},
- {"nGetStringBounds", "(JLjava/lang/String;IIILandroid/graphics/Rect;)V",
- (void*) PaintGlue::getStringBounds },
- {"nGetCharArrayBounds", "(J[CIIILandroid/graphics/Rect;)V",
- (void*) PaintGlue::getCharArrayBounds },
- {"nHasGlyph", "(JILjava/lang/String;)Z", (void*) PaintGlue::hasGlyph },
- {"nGetRunAdvance", "(J[CIIIIZI)F", (void*) PaintGlue::getRunAdvance___CIIIIZI_F},
- {"nGetOffsetForAdvance", "(J[CIIIIZF)I",
- (void*) PaintGlue::getOffsetForAdvance___CIIIIZF_I},
- {"nGetFontMetricsIntForText", "(J[CIIIIZLandroid/graphics/Paint$FontMetricsInt;)V",
- (void*)PaintGlue::getFontMetricsIntForText___C},
- {"nGetFontMetricsIntForText",
- "(JLjava/lang/String;IIIIZLandroid/graphics/Paint$FontMetricsInt;)V",
- (void*)PaintGlue::getFontMetricsIntForText___String},
-
- // --------------- @FastNative ----------------------
-
- {"nSetTextLocales","(JLjava/lang/String;)I", (void*) PaintGlue::setTextLocales},
- {"nSetFontFeatureSettings","(JLjava/lang/String;)V",
- (void*) PaintGlue::setFontFeatureSettings},
- {"nGetFontMetrics", "(JLandroid/graphics/Paint$FontMetrics;)F",
- (void*)PaintGlue::getFontMetrics},
- {"nGetFontMetricsInt", "(JLandroid/graphics/Paint$FontMetricsInt;)I",
- (void*)PaintGlue::getFontMetricsInt},
-
- // --------------- @CriticalNative ------------------
-
- {"nReset","(J)V", (void*) PaintGlue::reset},
- {"nSet","(JJ)V", (void*) PaintGlue::assign},
- {"nGetFlags","(J)I", (void*) PaintGlue::getFlags},
- {"nSetFlags","(JI)V", (void*) PaintGlue::setFlags},
- {"nGetHinting","(J)I", (void*) PaintGlue::getHinting},
- {"nSetHinting","(JI)V", (void*) PaintGlue::setHinting},
- {"nSetAntiAlias","(JZ)V", (void*) PaintGlue::setAntiAlias},
- {"nSetSubpixelText","(JZ)V", (void*) PaintGlue::setSubpixelText},
- {"nSetLinearText","(JZ)V", (void*) PaintGlue::setLinearText},
- {"nSetUnderlineText","(JZ)V", (void*) PaintGlue::setUnderlineText},
- {"nSetStrikeThruText","(JZ)V", (void*) PaintGlue::setStrikeThruText},
- {"nSetFakeBoldText","(JZ)V", (void*) PaintGlue::setFakeBoldText},
- {"nSetFilterBitmap","(JZ)V", (void*) PaintGlue::setFilterBitmap},
- {"nSetDither","(JZ)V", (void*) PaintGlue::setDither},
- {"nGetStyle","(J)I", (void*) PaintGlue::getStyle},
- {"nSetStyle","(JI)V", (void*) PaintGlue::setStyle},
- {"nSetColor","(JI)V", (void*) PaintGlue::setColor},
- {"nSetColor","(JJJ)V", (void*) PaintGlue::setColorLong},
- {"nSetAlpha","(JI)V", (void*) PaintGlue::setAlpha},
- {"nGetStrokeWidth","(J)F", (void*) PaintGlue::getStrokeWidth},
- {"nSetStrokeWidth","(JF)V", (void*) PaintGlue::setStrokeWidth},
- {"nGetStrokeMiter","(J)F", (void*) PaintGlue::getStrokeMiter},
- {"nSetStrokeMiter","(JF)V", (void*) PaintGlue::setStrokeMiter},
- {"nGetStrokeCap","(J)I", (void*) PaintGlue::getStrokeCap},
- {"nSetStrokeCap","(JI)V", (void*) PaintGlue::setStrokeCap},
- {"nGetStrokeJoin","(J)I", (void*) PaintGlue::getStrokeJoin},
- {"nSetStrokeJoin","(JI)V", (void*) PaintGlue::setStrokeJoin},
- {"nGetFillPath","(JJJ)Z", (void*) PaintGlue::getFillPath},
- {"nSetShader","(JJ)J", (void*) PaintGlue::setShader},
- {"nSetColorFilter","(JJ)J", (void*) PaintGlue::setColorFilter},
- {"nSetXfermode","(JI)V", (void*) PaintGlue::setXfermode},
- {"nSetPathEffect","(JJ)J", (void*) PaintGlue::setPathEffect},
- {"nSetMaskFilter","(JJ)J", (void*) PaintGlue::setMaskFilter},
- {"nSetTypeface","(JJ)V", (void*) PaintGlue::setTypeface},
- {"nGetTextAlign","(J)I", (void*) PaintGlue::getTextAlign},
- {"nSetTextAlign","(JI)V", (void*) PaintGlue::setTextAlign},
- {"nSetTextLocalesByMinikinLocaleListId","(JI)V",
- (void*) PaintGlue::setTextLocalesByMinikinLocaleListId},
- {"nIsElegantTextHeight","(J)Z", (void*) PaintGlue::isElegantTextHeight},
- {"nSetElegantTextHeight","(JZ)V", (void*) PaintGlue::setElegantTextHeight},
- {"nGetTextSize","(J)F", (void*) PaintGlue::getTextSize},
- {"nSetTextSize","(JF)V", (void*) PaintGlue::setTextSize},
- {"nGetTextScaleX","(J)F", (void*) PaintGlue::getTextScaleX},
- {"nSetTextScaleX","(JF)V", (void*) PaintGlue::setTextScaleX},
- {"nGetTextSkewX","(J)F", (void*) PaintGlue::getTextSkewX},
- {"nSetTextSkewX","(JF)V", (void*) PaintGlue::setTextSkewX},
- {"nGetLetterSpacing","(J)F", (void*) PaintGlue::getLetterSpacing},
- {"nSetLetterSpacing","(JF)V", (void*) PaintGlue::setLetterSpacing},
- {"nGetWordSpacing","(J)F", (void*) PaintGlue::getWordSpacing},
- {"nSetWordSpacing","(JF)V", (void*) PaintGlue::setWordSpacing},
- {"nGetStartHyphenEdit", "(J)I", (void*) PaintGlue::getStartHyphenEdit},
- {"nGetEndHyphenEdit", "(J)I", (void*) PaintGlue::getEndHyphenEdit},
- {"nSetStartHyphenEdit", "(JI)V", (void*) PaintGlue::setStartHyphenEdit},
- {"nSetEndHyphenEdit", "(JI)V", (void*) PaintGlue::setEndHyphenEdit},
- {"nAscent","(J)F", (void*) PaintGlue::ascent},
- {"nDescent","(J)F", (void*) PaintGlue::descent},
- {"nGetUnderlinePosition","(J)F", (void*) PaintGlue::getUnderlinePosition},
- {"nGetUnderlineThickness","(J)F", (void*) PaintGlue::getUnderlineThickness},
- {"nGetStrikeThruPosition","(J)F", (void*) PaintGlue::getStrikeThruPosition},
- {"nGetStrikeThruThickness","(J)F", (void*) PaintGlue::getStrikeThruThickness},
- {"nSetShadowLayer", "(JFFFJJ)V", (void*)PaintGlue::setShadowLayer},
- {"nHasShadowLayer", "(J)Z", (void*)PaintGlue::hasShadowLayer},
- {"nEqualsForTextMeasurement", "(JJ)Z", (void*)PaintGlue::equalsForTextMeasurement},
+ {"nGetNativeFinalizer", "()J", (void*)PaintGlue::getNativeFinalizer},
+ {"nInit", "()J", (void*)PaintGlue::init},
+ {"nInitWithPaint", "(J)J", (void*)PaintGlue::initWithPaint},
+ {"nBreakText", "(J[CIIFI[F)I", (void*)PaintGlue::breakTextC},
+ {"nBreakText", "(JLjava/lang/String;ZFI[F)I", (void*)PaintGlue::breakTextS},
+ {"nGetTextAdvances", "(J[CIIIII[FI)F", (void*)PaintGlue::getTextAdvances___CIIIII_FI},
+ {"nGetTextAdvances", "(JLjava/lang/String;IIIII[FI)F",
+ (void*)PaintGlue::getTextAdvances__StringIIIII_FI},
+
+ {"nGetTextRunCursor", "(J[CIIIII)I", (void*)PaintGlue::getTextRunCursor___C},
+ {"nGetTextRunCursor", "(JLjava/lang/String;IIIII)I",
+ (void*)PaintGlue::getTextRunCursor__String},
+ {"nGetTextPath", "(JI[CIIFFJ)V", (void*)PaintGlue::getTextPath___C},
+ {"nGetTextPath", "(JILjava/lang/String;IIFFJ)V", (void*)PaintGlue::getTextPath__String},
+ {"nGetStringBounds", "(JLjava/lang/String;IIILandroid/graphics/Rect;)V",
+ (void*)PaintGlue::getStringBounds},
+ {"nGetCharArrayBounds", "(J[CIIILandroid/graphics/Rect;)V",
+ (void*)PaintGlue::getCharArrayBounds},
+ {"nHasGlyph", "(JILjava/lang/String;)Z", (void*)PaintGlue::hasGlyph},
+ {"nGetRunAdvance", "(J[CIIIIZI)F", (void*)PaintGlue::getRunAdvance___CIIIIZI_F},
+ {"nGetRunCharacterAdvance", "(J[CIIIIZI[FI)F",
+ (void*)PaintGlue::getRunCharacterAdvance___CIIIIZI_FI_F},
+ {"nGetOffsetForAdvance", "(J[CIIIIZF)I", (void*)PaintGlue::getOffsetForAdvance___CIIIIZF_I},
+ {"nGetFontMetricsIntForText", "(J[CIIIIZLandroid/graphics/Paint$FontMetricsInt;)V",
+ (void*)PaintGlue::getFontMetricsIntForText___C},
+ {"nGetFontMetricsIntForText",
+ "(JLjava/lang/String;IIIIZLandroid/graphics/Paint$FontMetricsInt;)V",
+ (void*)PaintGlue::getFontMetricsIntForText___String},
+
+ // --------------- @FastNative ----------------------
+
+ {"nSetTextLocales", "(JLjava/lang/String;)I", (void*)PaintGlue::setTextLocales},
+ {"nSetFontFeatureSettings", "(JLjava/lang/String;)V",
+ (void*)PaintGlue::setFontFeatureSettings},
+ {"nGetFontMetrics", "(JLandroid/graphics/Paint$FontMetrics;)F",
+ (void*)PaintGlue::getFontMetrics},
+ {"nGetFontMetricsInt", "(JLandroid/graphics/Paint$FontMetricsInt;)I",
+ (void*)PaintGlue::getFontMetricsInt},
+
+ // --------------- @CriticalNative ------------------
+
+ {"nReset", "(J)V", (void*)PaintGlue::reset},
+ {"nSet", "(JJ)V", (void*)PaintGlue::assign},
+ {"nGetFlags", "(J)I", (void*)PaintGlue::getFlags},
+ {"nSetFlags", "(JI)V", (void*)PaintGlue::setFlags},
+ {"nGetHinting", "(J)I", (void*)PaintGlue::getHinting},
+ {"nSetHinting", "(JI)V", (void*)PaintGlue::setHinting},
+ {"nSetAntiAlias", "(JZ)V", (void*)PaintGlue::setAntiAlias},
+ {"nSetSubpixelText", "(JZ)V", (void*)PaintGlue::setSubpixelText},
+ {"nSetLinearText", "(JZ)V", (void*)PaintGlue::setLinearText},
+ {"nSetUnderlineText", "(JZ)V", (void*)PaintGlue::setUnderlineText},
+ {"nSetStrikeThruText", "(JZ)V", (void*)PaintGlue::setStrikeThruText},
+ {"nSetFakeBoldText", "(JZ)V", (void*)PaintGlue::setFakeBoldText},
+ {"nSetFilterBitmap", "(JZ)V", (void*)PaintGlue::setFilterBitmap},
+ {"nSetDither", "(JZ)V", (void*)PaintGlue::setDither},
+ {"nGetStyle", "(J)I", (void*)PaintGlue::getStyle},
+ {"nSetStyle", "(JI)V", (void*)PaintGlue::setStyle},
+ {"nSetColor", "(JI)V", (void*)PaintGlue::setColor},
+ {"nSetColor", "(JJJ)V", (void*)PaintGlue::setColorLong},
+ {"nSetAlpha", "(JI)V", (void*)PaintGlue::setAlpha},
+ {"nGetStrokeWidth", "(J)F", (void*)PaintGlue::getStrokeWidth},
+ {"nSetStrokeWidth", "(JF)V", (void*)PaintGlue::setStrokeWidth},
+ {"nGetStrokeMiter", "(J)F", (void*)PaintGlue::getStrokeMiter},
+ {"nSetStrokeMiter", "(JF)V", (void*)PaintGlue::setStrokeMiter},
+ {"nGetStrokeCap", "(J)I", (void*)PaintGlue::getStrokeCap},
+ {"nSetStrokeCap", "(JI)V", (void*)PaintGlue::setStrokeCap},
+ {"nGetStrokeJoin", "(J)I", (void*)PaintGlue::getStrokeJoin},
+ {"nSetStrokeJoin", "(JI)V", (void*)PaintGlue::setStrokeJoin},
+ {"nGetFillPath", "(JJJ)Z", (void*)PaintGlue::getFillPath},
+ {"nSetShader", "(JJ)J", (void*)PaintGlue::setShader},
+ {"nSetColorFilter", "(JJ)J", (void*)PaintGlue::setColorFilter},
+ {"nSetXfermode", "(JI)V", (void*)PaintGlue::setXfermode},
+ {"nSetPathEffect", "(JJ)J", (void*)PaintGlue::setPathEffect},
+ {"nSetMaskFilter", "(JJ)J", (void*)PaintGlue::setMaskFilter},
+ {"nSetTypeface", "(JJ)V", (void*)PaintGlue::setTypeface},
+ {"nGetTextAlign", "(J)I", (void*)PaintGlue::getTextAlign},
+ {"nSetTextAlign", "(JI)V", (void*)PaintGlue::setTextAlign},
+ {"nSetTextLocalesByMinikinLocaleListId", "(JI)V",
+ (void*)PaintGlue::setTextLocalesByMinikinLocaleListId},
+ {"nIsElegantTextHeight", "(J)Z", (void*)PaintGlue::isElegantTextHeight},
+ {"nSetElegantTextHeight", "(JZ)V", (void*)PaintGlue::setElegantTextHeight},
+ {"nGetTextSize", "(J)F", (void*)PaintGlue::getTextSize},
+ {"nSetTextSize", "(JF)V", (void*)PaintGlue::setTextSize},
+ {"nGetTextScaleX", "(J)F", (void*)PaintGlue::getTextScaleX},
+ {"nSetTextScaleX", "(JF)V", (void*)PaintGlue::setTextScaleX},
+ {"nGetTextSkewX", "(J)F", (void*)PaintGlue::getTextSkewX},
+ {"nSetTextSkewX", "(JF)V", (void*)PaintGlue::setTextSkewX},
+ {"nGetLetterSpacing", "(J)F", (void*)PaintGlue::getLetterSpacing},
+ {"nSetLetterSpacing", "(JF)V", (void*)PaintGlue::setLetterSpacing},
+ {"nGetWordSpacing", "(J)F", (void*)PaintGlue::getWordSpacing},
+ {"nSetWordSpacing", "(JF)V", (void*)PaintGlue::setWordSpacing},
+ {"nGetStartHyphenEdit", "(J)I", (void*)PaintGlue::getStartHyphenEdit},
+ {"nGetEndHyphenEdit", "(J)I", (void*)PaintGlue::getEndHyphenEdit},
+ {"nSetStartHyphenEdit", "(JI)V", (void*)PaintGlue::setStartHyphenEdit},
+ {"nSetEndHyphenEdit", "(JI)V", (void*)PaintGlue::setEndHyphenEdit},
+ {"nAscent", "(J)F", (void*)PaintGlue::ascent},
+ {"nDescent", "(J)F", (void*)PaintGlue::descent},
+ {"nGetUnderlinePosition", "(J)F", (void*)PaintGlue::getUnderlinePosition},
+ {"nGetUnderlineThickness", "(J)F", (void*)PaintGlue::getUnderlineThickness},
+ {"nGetStrikeThruPosition", "(J)F", (void*)PaintGlue::getStrikeThruPosition},
+ {"nGetStrikeThruThickness", "(J)F", (void*)PaintGlue::getStrikeThruThickness},
+ {"nSetShadowLayer", "(JFFFJJ)V", (void*)PaintGlue::setShadowLayer},
+ {"nHasShadowLayer", "(J)Z", (void*)PaintGlue::hasShadowLayer},
+ {"nEqualsForTextMeasurement", "(JJ)Z", (void*)PaintGlue::equalsForTextMeasurement},
};
-
int register_android_graphics_Paint(JNIEnv* env) {
return RegisterMethodsOrDie(env, "android/graphics/Paint", methods, NELEM(methods));
}
--
cgit v1.2.3-59-g8ed1b
From 9feb92f9454cabc44cdce493ee11c34a4bc9b72b Mon Sep 17 00:00:00 2001
From: Kohsuke Yatoh
Date: Fri, 15 Jul 2022 06:03:07 +0000
Subject: Add Typeface memory perf test.
This test measures:
- SharedMemory (mmap) byte size of serialized font map.
- Native allocation (malloc) byte size of deserialized font map.
Typeface is updated to avoid OOM during test.
Results on oriole-userdebug:
[1/5] android.graphics.perftests.TypefaceSerializationPerfTest#testSerializeFontMap: PASSED (29.222s)
testSerializeFontMap_stddev (ns): 74859
testSerializeFontMap_mean (ns): 1560404
testSerializeFontMap_median (ns): 1549601
testSerializeFontMap_percentile90 (ns): 1602499
testSerializeFontMap_percentile95 (ns): 1624715
[2/5] android.graphics.perftests.TypefaceSerializationPerfTest#testSerializeFontMap_memory: PASSED (13.376s)
testSerializeFontMap_memory_stddev (ns): 0
testSerializeFontMap_memory_mean (ns): 931071
testSerializeFontMap_memory_median (ns): 931071
testSerializeFontMap_memory_percentile90 (ns): 931071
testSerializeFontMap_memory_percentile95 (ns): 931071
[3/5] android.graphics.perftests.TypefaceSerializationPerfTest#testDeserializeFontMap: PASSED (32.451s)
testDeserializeFontMap_stddev (ns): 60524
testDeserializeFontMap_mean (ns): 232285
testDeserializeFontMap_median (ns): 228149
testDeserializeFontMap_percentile90 (ns): 272094
testDeserializeFontMap_percentile95 (ns): 291708
[4/5] android.graphics.perftests.TypefaceSerializationPerfTest#testDeserializeFontMap_memory: PASSED (29.089s)
testDeserializeFontMap_memory_stddev (ns): 134
testDeserializeFontMap_memory_mean (ns): 170209
testDeserializeFontMap_memory_median (ns): 170208
testDeserializeFontMap_memory_percentile90 (ns): 170208
testDeserializeFontMap_memory_percentile95 (ns): 170208
[5/5] android.graphics.perftests.TypefaceSerializationPerfTest#testSetSystemFontMap: PASSED (28.687s)
testSetSystemFontMap_stddev (ns): 2345934
testSetSystemFontMap_mean (ns): 649662
testSetSystemFontMap_median (ns): 396260
testSetSystemFontMap_percentile90 (ns): 436483
testSetSystemFontMap_percentile95 (ns): 457479
Bug: 174672300
Fix: 238679890
Test: atest CorePerfTests:android.graphics.perftests.TypefaceSerializationPerfTest
Change-Id: Idb0c2a040d3ca488134f2b91a5bcbbe33a7a59c4
---
apct-tests/perftests/core/AndroidManifest.xml | 12 ++++
.../perftests/TypefaceSerializationPerfTest.java | 75 ++++++++++++++++++----
graphics/java/android/graphics/Typeface.java | 29 ++++++++-
3 files changed, 104 insertions(+), 12 deletions(-)
(limited to 'graphics/java')
diff --git a/apct-tests/perftests/core/AndroidManifest.xml b/apct-tests/perftests/core/AndroidManifest.xml
index 56fa70cfc220..bb57161b4f6b 100644
--- a/apct-tests/perftests/core/AndroidManifest.xml
+++ b/apct-tests/perftests/core/AndroidManifest.xml
@@ -1,5 +1,6 @@
@@ -33,6 +34,17 @@
android:exported="false"
android:process=":some_provider" />
+
+
+
+
+
systemFontMap = Typeface.getSystemFontMap();
- BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ ManualBenchmarkState state = mPerfManualStatusReporter.getBenchmarkState();
- while (state.keepRunning()) {
+ long elapsedTime = 0;
+ while (state.keepRunning(elapsedTime)) {
+ long startTime = System.nanoTime();
Typeface.serializeFontMap(systemFontMap);
+ elapsedTime = System.nanoTime() - startTime;
+ }
+ }
+
+ @Test
+ public void testSerializeFontMap_memory() throws Exception {
+ Map systemFontMap = Typeface.getSystemFontMap();
+ SharedMemory memory = Typeface.serializeFontMap(systemFontMap);
+ ManualBenchmarkState state = mPerfManualStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning(memory.getSize())) {
+ // Rate-limiting
+ SystemClock.sleep(100);
}
}
@@ -54,22 +74,54 @@ public class TypefaceSerializationPerfTest {
public void testDeserializeFontMap() throws Exception {
SharedMemory memory = Typeface.serializeFontMap(Typeface.getSystemFontMap());
ByteBuffer buffer = memory.mapReadOnly().order(ByteOrder.BIG_ENDIAN);
- BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ ManualBenchmarkState state = mPerfManualStatusReporter.getBenchmarkState();
+
+ ArrayMap out = new ArrayMap<>();
+ long elapsedTime = 0;
+ while (state.keepRunning(elapsedTime)) {
+ long startTime = System.nanoTime();
+ buffer.position(0);
+ Typeface.deserializeFontMap(buffer, out);
+ elapsedTime = System.nanoTime() - startTime;
+ }
+ }
+
+ @Test
+ public void testDeserializeFontMap_memory() throws Exception {
+ SharedMemory memory = Typeface.serializeFontMap(Typeface.getSystemFontMap());
+ ByteBuffer buffer = memory.mapReadOnly().order(ByteOrder.BIG_ENDIAN);
+ ManualBenchmarkState state = mPerfManualStatusReporter.getBenchmarkState();
ArrayMap out = new ArrayMap<>();
- while (state.keepRunning()) {
+ // Diff of native heap allocation size (in bytes) before and after deserializeFontMap.
+ // Note: we don't measure memory usage of setSystemFontMap because setSystemFontMap sets
+ // some global variables, and it's hard to clear them.
+ long heapDiff = 0;
+ // Sometimes heapDiff may become negative due to GC.
+ // Use 0 in that case to avoid crashing in keepRunning.
+ while (state.keepRunning(Math.max(0, heapDiff))) {
buffer.position(0);
+ long baselineSize = Debug.getNativeHeapAllocatedSize();
Typeface.deserializeFontMap(buffer, out);
+ long currentSize = Debug.getNativeHeapAllocatedSize();
+ heapDiff = currentSize - baselineSize;
+ Log.i(TAG, String.format("native heap alloc: current = %d, baseline = %d, diff = %d",
+ currentSize, baselineSize, heapDiff));
+ // Release native objects here to minimize the impact of GC.
+ for (Typeface typeface : out.values()) {
+ typeface.releaseNativeObjectForTest();
+ }
+ out.clear();
}
}
@Test
public void testSetSystemFontMap() throws Exception {
SharedMemory memory = null;
- BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ ManualBenchmarkState state = mPerfManualStatusReporter.getBenchmarkState();
- while (state.keepRunning()) {
- state.pauseTiming();
+ long elapsedTime = 0;
+ while (state.keepRunning(elapsedTime)) {
// Explicitly destroy lazy-loaded typefaces, so that we don't hit the mmap limit
// (max_map_count).
Typeface.destroySystemFontMap();
@@ -78,8 +130,9 @@ public class TypefaceSerializationPerfTest {
memory.close();
}
memory = Typeface.serializeFontMap(Typeface.getSystemFontMap());
- state.resumeTiming();
+ long startTime = System.nanoTime();
Typeface.setSystemFontMap(memory);
+ elapsedTime = System.nanoTime() - startTime;
}
}
}
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index a2f5301e353f..8e3acb8570f0 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -1176,6 +1176,17 @@ public class Typeface {
mWeight = nativeGetWeight(ni);
}
+ /**
+ * Releases the underlying native object.
+ *
+ * For testing only. Do not use the instance after this method is called.
+ * It is safe to call this method twice or more on the same instance.
+ * @hide
+ */
+ public void releaseNativeObjectForTest() {
+ mCleaner.run();
+ }
+
private static Typeface getSystemDefaultTypeface(@NonNull String familyName) {
Typeface tf = sSystemFontMap.get(familyName);
return tf == null ? Typeface.DEFAULT : tf;
@@ -1425,7 +1436,7 @@ public class Typeface {
public static void destroySystemFontMap() {
synchronized (SYSTEM_FONT_MAP_LOCK) {
for (Typeface typeface : sSystemFontMap.values()) {
- typeface.mCleaner.run();
+ typeface.releaseNativeObjectForTest();
}
sSystemFontMap.clear();
if (sSystemFontMapBuffer != null) {
@@ -1433,7 +1444,23 @@ public class Typeface {
}
sSystemFontMapBuffer = null;
sSystemFontMapSharedMemory = null;
+ synchronized (sStyledCacheLock) {
+ destroyTypefaceCacheLocked(sStyledTypefaceCache);
+ }
+ synchronized (sWeightCacheLock) {
+ destroyTypefaceCacheLocked(sWeightTypefaceCache);
+ }
+ }
+ }
+
+ private static void destroyTypefaceCacheLocked(LongSparseArray> cache) {
+ for (int i = 0; i < cache.size(); i++) {
+ SparseArray array = cache.valueAt(i);
+ for (int j = 0; j < array.size(); j++) {
+ array.valueAt(j).releaseNativeObjectForTest();
+ }
}
+ cache.clear();
}
/** @hide */
--
cgit v1.2.3-59-g8ed1b
From bb4e06fde0f19884ca30f676e4370b3b5f7833f6 Mon Sep 17 00:00:00 2001
From: Kohsuke Yatoh
Date: Tue, 19 Jul 2022 06:10:24 +0000
Subject: Preload minikin LocaleListCache in Zygote.
This saves ~31 KB per process on oriole.
Bug: 174672300
Test: atest CtsGraphicsTestCases:android.graphics.cts.TypefaceTest
Change-Id: Ic70ea842985a0aca441a31327e41980ed47bc961
---
graphics/java/android/graphics/Typeface.java | 7 +++++++
libs/hwui/jni/Typeface.cpp | 15 ++++++++++++---
2 files changed, 19 insertions(+), 3 deletions(-)
(limited to 'graphics/java')
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index a2f5301e353f..4ba6fba69de5 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -57,6 +57,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
import libcore.util.NativeAllocationRegistry;
@@ -1395,6 +1396,9 @@ public class Typeface {
FontConfig config = SystemFonts.getSystemPreinstalledFontConfig();
for (int i = 0; i < config.getFontFamilies().size(); ++i) {
FontConfig.FontFamily family = config.getFontFamilies().get(i);
+ if (!family.getLocaleList().isEmpty()) {
+ nativeRegisterLocaleList(family.getLocaleList().toLanguageTags());
+ }
boolean loadFamily = false;
for (int j = 0; j < family.getLocaleList().size(); ++j) {
String fontScript = ULocale.addLikelySubtags(
@@ -1541,4 +1545,7 @@ public class Typeface {
private static native void nativeAddFontCollections(long nativePtr);
private static native void nativeWarmUpCache(String fileName);
+
+ @FastNative
+ private static native void nativeRegisterLocaleList(String locales);
}
diff --git a/libs/hwui/jni/Typeface.cpp b/libs/hwui/jni/Typeface.cpp
index d86d9ee56f4c..e79b395412b0 100644
--- a/libs/hwui/jni/Typeface.cpp
+++ b/libs/hwui/jni/Typeface.cpp
@@ -20,18 +20,20 @@
#include
#include
#include
+#include
#include
#include
#include
+
+#include
+#include
+
#include "FontUtils.h"
#include "GraphicsJNI.h"
#include "SkData.h"
#include "SkTypeface.h"
#include "fonts/Font.h"
-#include
-#include
-
#ifdef __ANDROID__
#include
#endif
@@ -380,6 +382,12 @@ static void Typeface_addFontCollection(CRITICAL_JNI_PARAMS_COMMA jlong faceHandl
minikin::SystemFonts::addFontMap(std::move(collection));
}
+// Fast Native
+static void Typeface_registerLocaleList(JNIEnv* env, jobject, jstring jLocales) {
+ ScopedUtfChars locales(env, jLocales);
+ minikin::registerLocaleList(locales.c_str());
+}
+
///////////////////////////////////////////////////////////////////////////////
static const JNINativeMethod gTypefaceMethods[] = {
@@ -405,6 +413,7 @@ static const JNINativeMethod gTypefaceMethods[] = {
{"nativeGetFamily", "(JI)J", (void*)Typeface_getFamily},
{"nativeWarmUpCache", "(Ljava/lang/String;)V", (void*)Typeface_warmUpCache},
{"nativeAddFontCollections", "(J)V", (void*)Typeface_addFontCollection},
+ {"nativeRegisterLocaleList", "(Ljava/lang/String;)V", (void*)Typeface_registerLocaleList},
};
int register_android_graphics_Typeface(JNIEnv* env)
--
cgit v1.2.3-59-g8ed1b
From a2ffe9084acf4102de59e62216f0b957dbe70a13 Mon Sep 17 00:00:00 2001
From: Seigo Nonaka
Date: Thu, 21 Jul 2022 14:12:14 +0900
Subject: Add TestApi that change default typeface
Bug: 238597089
Test: atest android.theme.cts.ThemeHostTest#testThemes
Change-Id: Ia56b4424ab27b2f8dd01c0e61a1f9d26bd661fc9
---
core/api/test-current.txt | 1 +
graphics/java/android/graphics/Typeface.java | 36 ++++++++++++++++++++++++++++
2 files changed, 37 insertions(+)
(limited to 'graphics/java')
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index b13ebf388d1f..221cbdcfd4d7 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1026,6 +1026,7 @@ package android.graphics {
}
public class Typeface {
+ method @NonNull public static android.util.Pair,java.util.List> changeDefaultFontForTest(@NonNull java.util.List, @NonNull java.util.List);
method @NonNull public static long[] deserializeFontMap(@NonNull java.nio.ByteBuffer, @NonNull java.util.Map) throws java.io.IOException;
method @Nullable public static android.os.SharedMemory getSystemFontMapSharedMemory();
method @NonNull public static android.os.SharedMemory serializeFontMap(@NonNull java.util.Map) throws android.system.ErrnoException, java.io.IOException;
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index a2f5301e353f..d6692a968e45 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -50,6 +50,7 @@ import android.util.Base64;
import android.util.Log;
import android.util.LongSparseArray;
import android.util.LruCache;
+import android.util.Pair;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
@@ -1384,6 +1385,41 @@ public class Typeface {
}
}
+ /**
+ * Change default typefaces for testing purpose.
+ *
+ * Note: The existing TextView or Paint instance still holds the old Typeface.
+ *
+ * @param defaults array of [default, default_bold, default_italic, default_bolditalic].
+ * @param genericFamilies array of [sans-serif, serif, monospace]
+ * @return return the old defaults and genericFamilies
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ public static Pair, List> changeDefaultFontForTest(
+ @NonNull List defaults,
+ @NonNull List genericFamilies
+ ) {
+ synchronized (SYSTEM_FONT_MAP_LOCK) {
+ List oldDefaults = Arrays.asList(sDefaults);
+ sDefaults = defaults.toArray(new Typeface[4]);
+ setDefault(defaults.get(0));
+
+ ArrayList oldGenerics = new ArrayList<>();
+ oldGenerics.add(sSystemFontMap.get("sans-serif"));
+ sSystemFontMap.put("sans-serif", genericFamilies.get(0));
+
+ oldGenerics.add(sSystemFontMap.get("serif"));
+ sSystemFontMap.put("serif", genericFamilies.get(1));
+
+ oldGenerics.add(sSystemFontMap.get("monospace"));
+ sSystemFontMap.put("monospace", genericFamilies.get(2));
+
+ return new Pair<>(oldDefaults, oldGenerics);
+ }
+ }
+
static {
// Preload Roboto-Regular.ttf in Zygote for improving app launch performance.
preloadFontFile("/system/fonts/Roboto-Regular.ttf");
--
cgit v1.2.3-59-g8ed1b
From 711a120d16188b69a75792dcbf053c91916ca980 Mon Sep 17 00:00:00 2001
From: Kohsuke Yatoh
Date: Fri, 29 Jul 2022 00:46:39 +0000
Subject: Pass buffer base address to minikin::BufferReader.
Bug: 174672300
Test: atest CtsGraphicsTestCases:android.graphics.cts.TypefaceTest
Change-Id: Ie6bf1bb01ccc2803bc6206a0d2276ac59c6877a1
---
graphics/java/android/graphics/Typeface.java | 17 +++++++++++------
libs/hwui/jni/Typeface.cpp | 28 ++++++++++++++++++++--------
libs/hwui/jni/fonts/Font.cpp | 8 ++++----
3 files changed, 35 insertions(+), 18 deletions(-)
(limited to 'graphics/java')
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index f4f5e1e792ec..8ae0d3d48d6d 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -1244,14 +1244,16 @@ public class Typeface {
nativePtrs[i++] = entry.getValue().native_instance;
writeString(namesBytes, entry.getKey());
}
- int typefacesBytesCount = nativeWriteTypefaces(null, nativePtrs);
// int (typefacesBytesCount), typefaces, namesBytes
+ final int typefaceBytesCountSize = Integer.BYTES;
+ int typefacesBytesCount = nativeWriteTypefaces(null, typefaceBytesCountSize, nativePtrs);
SharedMemory sharedMemory = SharedMemory.create(
- "fontMap", Integer.BYTES + typefacesBytesCount + namesBytes.size());
+ "fontMap", typefaceBytesCountSize + typefacesBytesCount + namesBytes.size());
ByteBuffer writableBuffer = sharedMemory.mapReadWrite().order(ByteOrder.BIG_ENDIAN);
try {
writableBuffer.putInt(typefacesBytesCount);
- int writtenBytesCount = nativeWriteTypefaces(writableBuffer.slice(), nativePtrs);
+ int writtenBytesCount =
+ nativeWriteTypefaces(writableBuffer, writableBuffer.position(), nativePtrs);
if (writtenBytesCount != typefacesBytesCount) {
throw new IOException(String.format("Unexpected bytes written: %d, expected: %d",
writtenBytesCount, typefacesBytesCount));
@@ -1276,7 +1278,9 @@ public class Typeface {
@NonNull ByteBuffer buffer, @NonNull Map out)
throws IOException {
int typefacesBytesCount = buffer.getInt();
- long[] nativePtrs = nativeReadTypefaces(buffer.slice());
+ // Note: Do not call buffer.slice(), as nativeReadTypefaces() expects
+ // that buffer.address() is page-aligned.
+ long[] nativePtrs = nativeReadTypefaces(buffer, buffer.position());
if (nativePtrs == null) {
throw new IOException("Could not read typefaces");
}
@@ -1598,9 +1602,10 @@ public class Typeface {
private static native void nativeRegisterGenericFamily(String str, long nativePtr);
private static native int nativeWriteTypefaces(
- @Nullable ByteBuffer buffer, @NonNull long[] nativePtrs);
+ @Nullable ByteBuffer buffer, int position, @NonNull long[] nativePtrs);
- private static native @Nullable long[] nativeReadTypefaces(@NonNull ByteBuffer buffer);
+ private static native
+ @Nullable long[] nativeReadTypefaces(@NonNull ByteBuffer buffer, int position);
private static native void nativeForceSetStaticFinalField(String fieldName, Typeface typeface);
diff --git a/libs/hwui/jni/Typeface.cpp b/libs/hwui/jni/Typeface.cpp
index 19efc5f09d11..6325226068dd 100644
--- a/libs/hwui/jni/Typeface.cpp
+++ b/libs/hwui/jni/Typeface.cpp
@@ -305,7 +305,8 @@ void MinikinFontSkiaFactory::write(minikin::BufferWriter* writer,
}
}
-static jint Typeface_writeTypefaces(JNIEnv *env, jobject, jobject buffer, jlongArray faceHandles) {
+static jint Typeface_writeTypefaces(JNIEnv* env, jobject, jobject buffer, jint position,
+ jlongArray faceHandles) {
MinikinFontSkiaFactory::init();
ScopedLongArrayRO faces(env, faceHandles);
std::vector typefaces;
@@ -314,7 +315,12 @@ static jint Typeface_writeTypefaces(JNIEnv *env, jobject, jobject buffer, jlongA
typefaces.push_back(toTypeface(faces[i]));
}
void* addr = buffer == nullptr ? nullptr : env->GetDirectBufferAddress(buffer);
- minikin::BufferWriter writer(addr);
+ if (addr != nullptr &&
+ reinterpret_cast(addr) % minikin::BufferReader::kMaxAlignment != 0) {
+ ALOGE("addr (%p) must be aligned at kMaxAlignment, but it was not.", addr);
+ return 0;
+ }
+ minikin::BufferWriter writer(addr, position);
std::vector> fontCollections;
std::unordered_map, size_t> fcToIndex;
for (Typeface* typeface : typefaces) {
@@ -334,11 +340,18 @@ static jint Typeface_writeTypefaces(JNIEnv *env, jobject, jobject buffer, jlongA
return static_cast(writer.size());
}
-static jlongArray Typeface_readTypefaces(JNIEnv *env, jobject, jobject buffer) {
+static jlongArray Typeface_readTypefaces(JNIEnv* env, jobject, jobject buffer, jint position) {
MinikinFontSkiaFactory::init();
void* addr = buffer == nullptr ? nullptr : env->GetDirectBufferAddress(buffer);
- if (addr == nullptr) return nullptr;
- minikin::BufferReader reader(addr);
+ if (addr == nullptr) {
+ ALOGE("Passed a null buffer.");
+ return nullptr;
+ }
+ if (reinterpret_cast(addr) % minikin::BufferReader::kMaxAlignment != 0) {
+ ALOGE("addr (%p) must be aligned at kMaxAlignment, but it was not.", addr);
+ return nullptr;
+ }
+ minikin::BufferReader reader(addr, position);
std::vector> fontCollections =
minikin::FontCollection::readVector(&reader);
uint32_t typefaceCount = reader.read();
@@ -357,7 +370,6 @@ static jlongArray Typeface_readTypefaces(JNIEnv *env, jobject, jobject buffer) {
return result;
}
-
static void Typeface_forceSetStaticFinalField(JNIEnv *env, jclass cls, jstring fieldName,
jobject typeface) {
ScopedUtfChars fieldNameChars(env, fieldName);
@@ -417,8 +429,8 @@ static const JNINativeMethod gTypefaceMethods[] = {
{"nativeGetSupportedAxes", "(J)[I", (void*)Typeface_getSupportedAxes},
{"nativeRegisterGenericFamily", "(Ljava/lang/String;J)V",
(void*)Typeface_registerGenericFamily},
- {"nativeWriteTypefaces", "(Ljava/nio/ByteBuffer;[J)I", (void*)Typeface_writeTypefaces},
- {"nativeReadTypefaces", "(Ljava/nio/ByteBuffer;)[J", (void*)Typeface_readTypefaces},
+ {"nativeWriteTypefaces", "(Ljava/nio/ByteBuffer;I[J)I", (void*)Typeface_writeTypefaces},
+ {"nativeReadTypefaces", "(Ljava/nio/ByteBuffer;I)[J", (void*)Typeface_readTypefaces},
{"nativeForceSetStaticFinalField", "(Ljava/lang/String;Landroid/graphics/Typeface;)V",
(void*)Typeface_forceSetStaticFinalField},
{"nativeGetFamilySize", "(J)I", (void*)Typeface_getFamilySize},
diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp
index 2c421f8727a8..f17129c8d953 100644
--- a/libs/hwui/jni/fonts/Font.cpp
+++ b/libs/hwui/jni/fonts/Font.cpp
@@ -228,7 +228,7 @@ static jlong Font_getReleaseNativeFontFunc(CRITICAL_JNI_PARAMS) {
static jstring Font_getFontPath(JNIEnv* env, jobject, jlong fontPtr) {
FontWrapper* font = reinterpret_cast(fontPtr);
minikin::BufferReader reader = font->font->typefaceMetadataReader();
- if (reader.data() != nullptr) {
+ if (reader.current() != nullptr) {
std::string path = std::string(reader.readString());
if (path.empty()) {
return nullptr;
@@ -270,7 +270,7 @@ static jint Font_getPackedStyle(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) {
static jint Font_getIndex(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) {
FontWrapper* font = reinterpret_cast(fontPtr);
minikin::BufferReader reader = font->font->typefaceMetadataReader();
- if (reader.data() != nullptr) {
+ if (reader.current() != nullptr) {
reader.skipString(); // fontPath
return reader.read();
} else {
@@ -283,7 +283,7 @@ static jint Font_getIndex(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) {
static jint Font_getAxisCount(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) {
FontWrapper* font = reinterpret_cast(fontPtr);
minikin::BufferReader reader = font->font->typefaceMetadataReader();
- if (reader.data() != nullptr) {
+ if (reader.current() != nullptr) {
reader.skipString(); // fontPath
reader.skip(); // fontIndex
return reader.readArray().second;
@@ -298,7 +298,7 @@ static jlong Font_getAxisInfo(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr, jint inde
FontWrapper* font = reinterpret_cast(fontPtr);
minikin::BufferReader reader = font->font->typefaceMetadataReader();
minikin::FontVariation var;
- if (reader.data() != nullptr) {
+ if (reader.current() != nullptr) {
reader.skipString(); // fontPath
reader.skip(); // fontIndex
var = reader.readArray().first[index];
--
cgit v1.2.3-59-g8ed1b
From 4d73cb10437af19ef9a4d94a92edbb5642db148e Mon Sep 17 00:00:00 2001
From: John Reck
Date: Wed, 27 Jul 2022 10:32:52 -0400
Subject: Allow PixelCopy for a window from any View
Also make it actually async, and allow the bitmap
to be auto-allocated
Bug: 195673633
Test: PixelCopyTest CTS suite
Change-Id: Ie872f20c809eaaeb8dc32f3ec6347f21a9a7bc1a
---
core/api/current.txt | 15 ++
.../java/android/graphics/HardwareRenderer.java | 43 +++-
graphics/java/android/view/PixelCopy.java | 251 +++++++++++++++++++--
libs/hwui/CopyRequest.h | 42 ++++
libs/hwui/Readback.cpp | 79 ++-----
libs/hwui/Readback.h | 18 +-
.../hwui/jni/android_graphics_HardwareRenderer.cpp | 53 ++++-
libs/hwui/renderthread/RenderProxy.cpp | 11 +-
libs/hwui/renderthread/RenderProxy.h | 6 +-
.../tests/common/scenes/MagnifierAnimation.cpp | 51 ++++-
libs/hwui/thread/WorkQueue.h | 2 +-
11 files changed, 446 insertions(+), 125 deletions(-)
create mode 100644 libs/hwui/CopyRequest.h
(limited to 'graphics/java')
diff --git a/core/api/current.txt b/core/api/current.txt
index 7fbf3ece4550..0c2c580fc456 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -49219,6 +49219,10 @@ package android.view {
}
public final class PixelCopy {
+ method @NonNull public static android.view.PixelCopy.Request ofSurface(@NonNull android.view.Surface, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer);
+ method @NonNull public static android.view.PixelCopy.Request ofSurface(@NonNull android.view.SurfaceView, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer);
+ method @NonNull public static android.view.PixelCopy.Request ofWindow(@NonNull android.view.Window, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer);
+ method @NonNull public static android.view.PixelCopy.Request ofWindow(@NonNull android.view.View, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer);
method public static void request(@NonNull android.view.SurfaceView, @NonNull android.graphics.Bitmap, @NonNull android.view.PixelCopy.OnPixelCopyFinishedListener, @NonNull android.os.Handler);
method public static void request(@NonNull android.view.SurfaceView, @Nullable android.graphics.Rect, @NonNull android.graphics.Bitmap, @NonNull android.view.PixelCopy.OnPixelCopyFinishedListener, @NonNull android.os.Handler);
method public static void request(@NonNull android.view.Surface, @NonNull android.graphics.Bitmap, @NonNull android.view.PixelCopy.OnPixelCopyFinishedListener, @NonNull android.os.Handler);
@@ -49233,10 +49237,21 @@ package android.view {
field public static final int SUCCESS = 0; // 0x0
}
+ public static final class PixelCopy.CopyResult {
+ method @NonNull public android.graphics.Bitmap getBitmap();
+ method public int getStatus();
+ }
+
public static interface PixelCopy.OnPixelCopyFinishedListener {
method public void onPixelCopyFinished(int);
}
+ public static final class PixelCopy.Request {
+ method public void request();
+ method @NonNull public android.view.PixelCopy.Request setDestinationBitmap(@Nullable android.graphics.Bitmap);
+ method @NonNull public android.view.PixelCopy.Request setSourceRect(@Nullable android.graphics.Rect);
+ }
+
public final class PointerIcon implements android.os.Parcelable {
method @NonNull public static android.view.PointerIcon create(@NonNull android.graphics.Bitmap, float, float);
method public int describeContents();
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index 7cc22d753f69..0e67f1f4842a 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -1046,14 +1046,39 @@ public class HardwareRenderer {
}
/** @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());
+ public abstract static class CopyRequest {
+ protected Bitmap mDestinationBitmap;
+ final Rect mSrcRect;
+
+ protected CopyRequest(Rect srcRect, Bitmap destinationBitmap) {
+ mDestinationBitmap = destinationBitmap;
+ if (srcRect != null) {
+ mSrcRect = srcRect;
+ } else {
+ mSrcRect = new Rect();
+ }
+ }
+
+ /**
+ * Retrieve the bitmap in which to store the result of the copy request
+ */
+ public long getDestinationBitmap(int srcWidth, int srcHeight) {
+ if (mDestinationBitmap == null) {
+ mDestinationBitmap =
+ Bitmap.createBitmap(srcWidth, srcHeight, Bitmap.Config.ARGB_8888);
+ }
+ return mDestinationBitmap.getNativeInstance();
}
+
+ /** Called when the copy is completed */
+ public abstract void onCopyFinished(int result);
+ }
+
+ /** @hide */
+ public static void copySurfaceInto(Surface surface, CopyRequest copyRequest) {
+ final Rect srcRect = copyRequest.mSrcRect;
+ nCopySurfaceInto(surface, srcRect.left, srcRect.top, srcRect.right, srcRect.bottom,
+ copyRequest);
}
/**
@@ -1464,8 +1489,8 @@ public class HardwareRenderer {
private static native void nRemoveObserver(long nativeProxy, long nativeObserver);
- private static native int nCopySurfaceInto(Surface surface,
- int srcLeft, int srcTop, int srcRight, int srcBottom, long bitmapHandle);
+ private static native void nCopySurfaceInto(Surface surface,
+ int srcLeft, int srcTop, int srcRight, int srcBottom, CopyRequest request);
private static native Bitmap nCreateHardwareBitmap(long renderNode, int width, int height);
diff --git a/graphics/java/android/view/PixelCopy.java b/graphics/java/android/view/PixelCopy.java
index 2797a4daa925..0f82c8fe904a 100644
--- a/graphics/java/android/view/PixelCopy.java
+++ b/graphics/java/android/view/PixelCopy.java
@@ -20,12 +20,15 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Bitmap;
+import android.graphics.HardwareRenderer;
import android.graphics.Rect;
import android.os.Handler;
import android.view.ViewTreeObserver.OnDrawListener;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* Provides a mechanisms to issue pixel copy requests to allow for copy
@@ -183,12 +186,10 @@ public final class PixelCopy {
if (srcRect != null && srcRect.isEmpty()) {
throw new IllegalArgumentException("sourceRect is empty");
}
- // TODO: Make this actually async and fast and cool and stuff
- int result = ThreadedRenderer.copySurfaceInto(source, srcRect, dest);
- listenerThread.post(new Runnable() {
+ HardwareRenderer.copySurfaceInto(source, new HardwareRenderer.CopyRequest(srcRect, dest) {
@Override
- public void run() {
- listener.onPixelCopyFinished(result);
+ public void onCopyFinished(int result) {
+ listenerThread.post(() -> listener.onPixelCopyFinished(result));
}
});
}
@@ -255,6 +256,26 @@ public final class PixelCopy {
@NonNull Bitmap dest, @NonNull OnPixelCopyFinishedListener listener,
@NonNull Handler listenerThread) {
validateBitmapDest(dest);
+ final Rect insets = new Rect();
+ final Surface surface = sourceForWindow(source, insets);
+ request(surface, adjustSourceRectForInsets(insets, srcRect), dest, listener,
+ listenerThread);
+ }
+
+ private static void validateBitmapDest(Bitmap bitmap) {
+ // TODO: Pre-check max texture dimens if we can
+ if (bitmap == null) {
+ throw new IllegalArgumentException("Bitmap cannot be null");
+ }
+ if (bitmap.isRecycled()) {
+ throw new IllegalArgumentException("Bitmap is recycled");
+ }
+ if (!bitmap.isMutable()) {
+ throw new IllegalArgumentException("Bitmap is immutable");
+ }
+ }
+
+ private static Surface sourceForWindow(Window source, Rect outInsets) {
if (source == null) {
throw new IllegalArgumentException("source is null");
}
@@ -267,32 +288,222 @@ public final class PixelCopy {
if (root != null) {
surface = root.mSurface;
final Rect surfaceInsets = root.mWindowAttributes.surfaceInsets;
- if (srcRect == null) {
- srcRect = new Rect(surfaceInsets.left, surfaceInsets.top,
- root.mWidth + surfaceInsets.left, root.mHeight + surfaceInsets.top);
- } else {
- srcRect.offset(surfaceInsets.left, surfaceInsets.top);
- }
+ outInsets.set(surfaceInsets.left, surfaceInsets.top,
+ root.mWidth + surfaceInsets.left, root.mHeight + surfaceInsets.top);
}
if (surface == null || !surface.isValid()) {
throw new IllegalArgumentException(
"Window doesn't have a backing surface!");
}
- request(surface, srcRect, dest, listener, listenerThread);
+ return surface;
}
- private static void validateBitmapDest(Bitmap bitmap) {
- // TODO: Pre-check max texture dimens if we can
- if (bitmap == null) {
- throw new IllegalArgumentException("Bitmap cannot be null");
+ private static Rect adjustSourceRectForInsets(Rect insets, Rect srcRect) {
+ if (srcRect == null) {
+ return insets;
}
- if (bitmap.isRecycled()) {
- throw new IllegalArgumentException("Bitmap is recycled");
+ if (insets != null) {
+ srcRect.offset(insets.left, insets.top);
}
- if (!bitmap.isMutable()) {
- throw new IllegalArgumentException("Bitmap is immutable");
+ return srcRect;
+ }
+
+ /**
+ * Contains the result of a PixelCopy request
+ */
+ public static final class CopyResult {
+ private int mStatus;
+ private Bitmap mBitmap;
+
+ private CopyResult(@CopyResultStatus int status, Bitmap bitmap) {
+ mStatus = status;
+ mBitmap = bitmap;
+ }
+
+ /**
+ * Returns the {@link CopyResultStatus} of the copy request.
+ */
+ public @CopyResultStatus int getStatus() {
+ return mStatus;
+ }
+
+ private void validateStatus() {
+ if (mStatus != SUCCESS) {
+ throw new IllegalStateException("Copy request didn't succeed, status = " + mStatus);
+ }
+ }
+
+ /**
+ * If the PixelCopy {@link Request} was given a destination bitmap with
+ * {@link Request#setDestinationBitmap(Bitmap)} then the returned bitmap will be the same
+ * as the one given. If no destination bitmap was provided, then this
+ * will contain the automatically allocated Bitmap to hold the result.
+ *
+ * @return the Bitmap the copy request was stored in.
+ * @throws IllegalStateException if {@link #getStatus()} is not SUCCESS
+ */
+ public @NonNull Bitmap getBitmap() {
+ validateStatus();
+ return mBitmap;
+ }
+ }
+
+ /**
+ * A builder to create the complete PixelCopy request, which is then executed by calling
+ * {@link #request()}
+ */
+ public static final class Request {
+ private Request(Surface source, Rect sourceInsets, Executor listenerThread,
+ Consumer listener) {
+ this.mSource = source;
+ this.mSourceInsets = sourceInsets;
+ this.mListenerThread = listenerThread;
+ this.mListener = listener;
+ }
+
+ private final Surface mSource;
+ private final Consumer mListener;
+ private final Executor mListenerThread;
+ private final Rect mSourceInsets;
+ private Rect mSrcRect;
+ private Bitmap mDest;
+
+ /**
+ * Sets the region of the source to copy from. By default, the entire source is copied to
+ * the output. If only a subset of the source is necessary to be copied, specifying a
+ * srcRect will improve performance by reducing
+ * the amount of data being copied.
+ *
+ * @param srcRect The area of the source to read from. Null or empty will be treated to
+ * mean the entire source
+ * @return this
+ */
+ public @NonNull Request setSourceRect(@Nullable Rect srcRect) {
+ this.mSrcRect = srcRect;
+ return this;
+ }
+
+ /**
+ * Specifies the output bitmap in which to store the result. By default, a Bitmap of format
+ * {@link android.graphics.Bitmap.Config#ARGB_8888} with a width & height matching that
+ * of the {@link #setSourceRect(Rect) source area} will be created to place the result.
+ *
+ * @param destination The bitmap to store the result, or null to have a bitmap
+ * automatically created of the appropriate size. If not null, must not
+ * be {@link Bitmap#isRecycled() recycled} and must be
+ * {@link Bitmap#isMutable() mutable}.
+ * @return this
+ */
+ public @NonNull Request setDestinationBitmap(@Nullable Bitmap destination) {
+ if (destination != null) {
+ validateBitmapDest(destination);
+ }
+ this.mDest = destination;
+ return this;
+ }
+
+ /**
+ * Executes the request.
+ */
+ public void request() {
+ if (!mSource.isValid()) {
+ mListenerThread.execute(() -> mListener.accept(
+ new CopyResult(ERROR_SOURCE_INVALID, null)));
+ return;
+ }
+ HardwareRenderer.copySurfaceInto(mSource, new HardwareRenderer.CopyRequest(
+ adjustSourceRectForInsets(mSourceInsets, mSrcRect), mDest) {
+ @Override
+ public void onCopyFinished(int result) {
+ mListenerThread.execute(() -> mListener.accept(
+ new CopyResult(result, mDestinationBitmap)));
+ }
+ });
}
}
+ /**
+ * Creates a PixelCopy request for the given {@link Window}
+ * @param source The Window to copy from
+ * @param callbackExecutor The executor to run the callback on
+ * @param listener The callback for when the copy request is completed
+ * @return A {@link Request} builder to set the optional params & execute the request
+ */
+ public static @NonNull Request ofWindow(@NonNull Window source,
+ @NonNull Executor callbackExecutor,
+ @NonNull Consumer listener) {
+ final Rect insets = new Rect();
+ final Surface surface = sourceForWindow(source, insets);
+ return new Request(surface, insets, callbackExecutor, listener);
+ }
+
+ /**
+ * Creates a PixelCopy request for the {@link Window} that the given {@link View} is
+ * attached to.
+ *
+ * Note that this copy request is not cropped to the area the View occupies by default. If that
+ * behavior is desired, use {@link View#getLocationInWindow(int[])} combined with
+ * {@link Request#setSourceRect(Rect)} to set a crop area to restrict the copy operation.
+ *
+ * @param source A View that {@link View#isAttachedToWindow() is attached} to a window that
+ * will be used to retrieve the window to copy from.
+ * @param callbackExecutor The executor to run the callback on
+ * @param listener The callback for when the copy request is completed
+ * @return A {@link Request} builder to set the optional params & execute the request
+ */
+ public static @NonNull Request ofWindow(@NonNull View source,
+ @NonNull Executor callbackExecutor,
+ @NonNull Consumer listener) {
+ if (source == null || !source.isAttachedToWindow()) {
+ throw new IllegalArgumentException(
+ "View must not be null & must be attached to window");
+ }
+ final Rect insets = new Rect();
+ Surface surface = null;
+ final ViewRootImpl root = source.getViewRootImpl();
+ if (root != null) {
+ surface = root.mSurface;
+ insets.set(root.mWindowAttributes.surfaceInsets);
+ }
+ if (surface == null || !surface.isValid()) {
+ throw new IllegalArgumentException(
+ "Window doesn't have a backing surface!");
+ }
+ return new Request(surface, insets, callbackExecutor, listener);
+ }
+
+ /**
+ * Creates a PixelCopy request for the given {@link Surface}
+ *
+ * @param source The Surface to copy from. Must be {@link Surface#isValid() valid}.
+ * @param callbackExecutor The executor to run the callback on
+ * @param listener The callback for when the copy request is completed
+ * @return A {@link Request} builder to set the optional params & execute the request
+ */
+ public static @NonNull Request ofSurface(@NonNull Surface source,
+ @NonNull Executor callbackExecutor,
+ @NonNull Consumer listener) {
+ if (source == null || !source.isValid()) {
+ throw new IllegalArgumentException("Source must not be null & must be valid");
+ }
+ return new Request(source, null, callbackExecutor, listener);
+ }
+
+ /**
+ * Creates a PixelCopy request for the {@link Surface} belonging to the
+ * given {@link SurfaceView}
+ *
+ * @param source The SurfaceView to copy from. The backing surface must be
+ * {@link Surface#isValid() valid}
+ * @param callbackExecutor The executor to run the callback on
+ * @param listener The callback for when the copy request is completed
+ * @return A {@link Request} builder to set the optional params & execute the request
+ */
+ public static @NonNull Request ofSurface(@NonNull SurfaceView source,
+ @NonNull Executor callbackExecutor,
+ @NonNull Consumer listener) {
+ return ofSurface(source.getHolder().getSurface(), callbackExecutor, listener);
+ }
+
private PixelCopy() {}
}
diff --git a/libs/hwui/CopyRequest.h b/libs/hwui/CopyRequest.h
new file mode 100644
index 000000000000..5fbd5f900716
--- /dev/null
+++ b/libs/hwui/CopyRequest.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#pragma once
+
+#include "Rect.h"
+#include "hwui/Bitmap.h"
+
+namespace android::uirenderer {
+
+// Keep in sync with PixelCopy.java codes
+enum class CopyResult {
+ Success = 0,
+ UnknownError = 1,
+ Timeout = 2,
+ SourceEmpty = 3,
+ SourceInvalid = 4,
+ DestinationInvalid = 5,
+};
+
+struct CopyRequest {
+ Rect srcRect;
+ CopyRequest(Rect srcRect) : srcRect(srcRect) {}
+ virtual ~CopyRequest() {}
+ virtual SkBitmap getDestinationBitmap(int srcWidth, int srcHeight) = 0;
+ virtual void onCopyFinished(CopyResult result) = 0;
+};
+
+} // namespace android::uirenderer
diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp
index 1191b92a0aeb..02c2e67a319b 100644
--- a/libs/hwui/Readback.cpp
+++ b/libs/hwui/Readback.cpp
@@ -49,8 +49,7 @@ namespace uirenderer {
#define ARECT_ARGS(r) float((r).left), float((r).top), float((r).right), float((r).bottom)
-CopyResult Readback::copySurfaceInto(ANativeWindow* window, const Rect& inSrcRect,
- SkBitmap* bitmap) {
+void Readback::copySurfaceInto(ANativeWindow* window, const std::shared_ptr& request) {
ATRACE_CALL();
// Setup the source
AHardwareBuffer* rawSourceBuffer;
@@ -63,30 +62,33 @@ CopyResult Readback::copySurfaceInto(ANativeWindow* window, const Rect& inSrcRec
// Really this shouldn't ever happen, but better safe than sorry.
if (err == UNKNOWN_TRANSACTION) {
ALOGW("Readback failed to ANativeWindow_getLastQueuedBuffer2 - who are we talking to?");
- return copySurfaceIntoLegacy(window, inSrcRect, bitmap);
+ return request->onCopyFinished(CopyResult::SourceInvalid);
}
ALOGV("Using new path, cropRect=" RECT_STRING ", transform=%x", ARECT_ARGS(cropRect),
windowTransform);
if (err != NO_ERROR) {
ALOGW("Failed to get last queued buffer, error = %d", err);
- return CopyResult::UnknownError;
+ return request->onCopyFinished(CopyResult::SourceInvalid);
}
if (rawSourceBuffer == nullptr) {
ALOGW("Surface doesn't have any previously queued frames, nothing to readback from");
- return CopyResult::SourceEmpty;
+ return request->onCopyFinished(CopyResult::SourceEmpty);
}
UniqueAHardwareBuffer sourceBuffer{rawSourceBuffer};
AHardwareBuffer_Desc description;
AHardwareBuffer_describe(sourceBuffer.get(), &description);
if (description.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT) {
ALOGW("Surface is protected, unable to copy from it");
- return CopyResult::SourceInvalid;
+ return request->onCopyFinished(CopyResult::SourceInvalid);
}
- if (sourceFence != -1 && sync_wait(sourceFence.get(), 500 /* ms */) != NO_ERROR) {
- ALOGE("Timeout (500ms) exceeded waiting for buffer fence, abandoning readback attempt");
- return CopyResult::Timeout;
+ {
+ ATRACE_NAME("sync_wait");
+ if (sourceFence != -1 && sync_wait(sourceFence.get(), 500 /* ms */) != NO_ERROR) {
+ ALOGE("Timeout (500ms) exceeded waiting for buffer fence, abandoning readback attempt");
+ return request->onCopyFinished(CopyResult::Timeout);
+ }
}
sk_sp colorSpace = DataSpaceToColorSpace(
@@ -95,12 +97,12 @@ CopyResult Readback::copySurfaceInto(ANativeWindow* window, const Rect& inSrcRec
SkImage::MakeFromAHardwareBuffer(sourceBuffer.get(), kPremul_SkAlphaType, colorSpace);
if (!image.get()) {
- return CopyResult::UnknownError;
+ return request->onCopyFinished(CopyResult::UnknownError);
}
sk_sp grContext = mRenderThread.requireGrContext();
- SkRect srcRect = inSrcRect.toSkRect();
+ SkRect srcRect = request->srcRect.toSkRect();
SkRect imageSrcRect = SkRect::MakeIWH(description.width, description.height);
SkISize imageWH = SkISize::Make(description.width, description.height);
@@ -148,10 +150,12 @@ CopyResult Readback::copySurfaceInto(ANativeWindow* window, const Rect& inSrcRec
ALOGV("intersecting " RECT_STRING " with " RECT_STRING, SK_RECT_ARGS(srcRect),
SK_RECT_ARGS(textureRect));
if (!srcRect.intersect(textureRect)) {
- return CopyResult::UnknownError;
+ return request->onCopyFinished(CopyResult::UnknownError);
}
}
+ SkBitmap skBitmap = request->getDestinationBitmap(srcRect.width(), srcRect.height());
+ SkBitmap* bitmap = &skBitmap;
sk_sp tmpSurface =
SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), SkBudgeted::kYes,
bitmap->info(), 0, kTopLeft_GrSurfaceOrigin, nullptr);
@@ -164,7 +168,7 @@ CopyResult Readback::copySurfaceInto(ANativeWindow* window, const Rect& inSrcRec
tmpInfo, 0, kTopLeft_GrSurfaceOrigin, nullptr);
if (!tmpSurface.get()) {
ALOGW("Unable to generate GPU buffer in a format compatible with the provided bitmap");
- return CopyResult::UnknownError;
+ return request->onCopyFinished(CopyResult::UnknownError);
}
}
@@ -235,52 +239,13 @@ CopyResult Readback::copySurfaceInto(ANativeWindow* window, const Rect& inSrcRec
!tmpBitmap.tryAllocPixels(tmpInfo) || !tmpSurface->readPixels(tmpBitmap, 0, 0) ||
!tmpBitmap.readPixels(bitmap->info(), bitmap->getPixels(), bitmap->rowBytes(), 0, 0)) {
ALOGW("Unable to convert content into the provided bitmap");
- return CopyResult::UnknownError;
+ return request->onCopyFinished(CopyResult::UnknownError);
}
}
bitmap->notifyPixelsChanged();
- return CopyResult::Success;
-}
-
-CopyResult Readback::copySurfaceIntoLegacy(ANativeWindow* window, const Rect& srcRect,
- SkBitmap* bitmap) {
- // Setup the source
- AHardwareBuffer* rawSourceBuffer;
- int rawSourceFence;
- Matrix4 texTransform;
- status_t err = ANativeWindow_getLastQueuedBuffer(window, &rawSourceBuffer, &rawSourceFence,
- texTransform.data);
- base::unique_fd sourceFence(rawSourceFence);
- texTransform.invalidateType();
- if (err != NO_ERROR) {
- ALOGW("Failed to get last queued buffer, error = %d", err);
- return CopyResult::UnknownError;
- }
- if (rawSourceBuffer == nullptr) {
- ALOGW("Surface doesn't have any previously queued frames, nothing to readback from");
- return CopyResult::SourceEmpty;
- }
-
- UniqueAHardwareBuffer sourceBuffer{rawSourceBuffer};
- AHardwareBuffer_Desc description;
- AHardwareBuffer_describe(sourceBuffer.get(), &description);
- if (description.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT) {
- ALOGW("Surface is protected, unable to copy from it");
- return CopyResult::SourceInvalid;
- }
-
- if (sourceFence != -1 && sync_wait(sourceFence.get(), 500 /* ms */) != NO_ERROR) {
- ALOGE("Timeout (500ms) exceeded waiting for buffer fence, abandoning readback attempt");
- return CopyResult::Timeout;
- }
-
- sk_sp colorSpace = DataSpaceToColorSpace(
- static_cast(ANativeWindow_getBuffersDataSpace(window)));
- sk_sp image =
- SkImage::MakeFromAHardwareBuffer(sourceBuffer.get(), kPremul_SkAlphaType, colorSpace);
- return copyImageInto(image, srcRect, bitmap);
+ return request->onCopyFinished(CopyResult::Success);
}
CopyResult Readback::copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap) {
@@ -318,14 +283,14 @@ CopyResult Readback::copyImageInto(const sk_sp& image, SkBitmap* bitmap
CopyResult Readback::copyImageInto(const sk_sp& image, const Rect& srcRect,
SkBitmap* bitmap) {
ATRACE_CALL();
+ if (!image.get()) {
+ return CopyResult::UnknownError;
+ }
if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaGL) {
mRenderThread.requireGlContext();
} else {
mRenderThread.requireVkContext();
}
- if (!image.get()) {
- return CopyResult::UnknownError;
- }
int imgWidth = image->width();
int imgHeight = image->height();
sk_sp grContext = sk_ref_sp(mRenderThread.getGrContext());
diff --git a/libs/hwui/Readback.h b/libs/hwui/Readback.h
index aa6e43c3bc27..a092d472abf0 100644
--- a/libs/hwui/Readback.h
+++ b/libs/hwui/Readback.h
@@ -16,12 +16,13 @@
#pragma once
+#include
+
+#include "CopyRequest.h"
#include "Matrix.h"
#include "Rect.h"
#include "renderthread/RenderThread.h"
-#include
-
class SkBitmap;
class SkImage;
struct SkRect;
@@ -35,23 +36,13 @@ namespace uirenderer {
class DeferredLayerUpdater;
class Layer;
-// Keep in sync with PixelCopy.java codes
-enum class CopyResult {
- Success = 0,
- UnknownError = 1,
- Timeout = 2,
- SourceEmpty = 3,
- SourceInvalid = 4,
- DestinationInvalid = 5,
-};
-
class Readback {
public:
explicit Readback(renderthread::RenderThread& thread) : mRenderThread(thread) {}
/**
* Copies the surface's most recently queued buffer into the provided bitmap.
*/
- CopyResult copySurfaceInto(ANativeWindow* window, const Rect& srcRect, SkBitmap* bitmap);
+ void copySurfaceInto(ANativeWindow* window, const std::shared_ptr& request);
CopyResult copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap);
CopyResult copyImageInto(const sk_sp& image, SkBitmap* bitmap);
@@ -59,7 +50,6 @@ public:
CopyResult copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap);
private:
- CopyResult copySurfaceIntoLegacy(ANativeWindow* window, const Rect& srcRect, SkBitmap* bitmap);
CopyResult copyImageInto(const sk_sp& image, const Rect& srcRect, SkBitmap* bitmap);
bool copyLayerInto(Layer* layer, const SkRect* srcRect, const SkRect* dstRect,
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index 55b1f23d294a..4f281fcde61d 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -88,6 +88,11 @@ struct {
jmethodID onFrameComplete;
} gFrameCompleteCallback;
+struct {
+ jmethodID onCopyFinished;
+ jmethodID getDestinationBitmap;
+} gCopyRequest;
+
static JNIEnv* getenv(JavaVM* vm) {
JNIEnv* env;
if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) {
@@ -672,15 +677,41 @@ static void android_view_ThreadedRenderer_setFrameCompleteCallback(JNIEnv* env,
}
}
-static jint android_view_ThreadedRenderer_copySurfaceInto(JNIEnv* env,
- jobject clazz, jobject jsurface, jint left, jint top,
- jint right, jint bottom, jlong bitmapPtr) {
- SkBitmap bitmap;
- bitmap::toBitmap(bitmapPtr).getSkBitmap(&bitmap);
+class CopyRequestAdapter : public CopyRequest {
+public:
+ CopyRequestAdapter(JavaVM* vm, jobject jCopyRequest, Rect srcRect)
+ : CopyRequest(srcRect), mRefHolder(vm, jCopyRequest) {}
+
+ virtual SkBitmap getDestinationBitmap(int srcWidth, int srcHeight) override {
+ JNIEnv* env = getenv(mRefHolder.vm());
+ jlong bitmapPtr = env->CallLongMethod(
+ mRefHolder.object(), gCopyRequest.getDestinationBitmap, srcWidth, srcHeight);
+ SkBitmap bitmap;
+ bitmap::toBitmap(bitmapPtr).getSkBitmap(&bitmap);
+ return bitmap;
+ }
+
+ virtual void onCopyFinished(CopyResult result) override {
+ JNIEnv* env = getenv(mRefHolder.vm());
+ env->CallVoidMethod(mRefHolder.object(), gCopyRequest.onCopyFinished,
+ static_cast(result));
+ }
+
+private:
+ JGlobalRefHolder mRefHolder;
+};
+
+static void android_view_ThreadedRenderer_copySurfaceInto(JNIEnv* env, jobject clazz,
+ jobject jsurface, jint left, jint top,
+ jint right, jint bottom,
+ jobject jCopyRequest) {
+ JavaVM* vm = nullptr;
+ LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Unable to get Java VM");
+ auto copyRequest = std::make_shared(vm, env->NewGlobalRef(jCopyRequest),
+ Rect(left, top, right, bottom));
ANativeWindow* window = fromSurface(env, jsurface);
- jint result = RenderProxy::copySurfaceInto(window, left, top, right, bottom, &bitmap);
+ RenderProxy::copySurfaceInto(window, std::move(copyRequest));
ANativeWindow_release(window);
- return result;
}
class ContextFactory : public IContextFactory {
@@ -969,7 +1000,8 @@ static const JNINativeMethod gMethods[] = {
(void*)android_view_ThreadedRenderer_setFrameCompleteCallback},
{"nAddObserver", "(JJ)V", (void*)android_view_ThreadedRenderer_addObserver},
{"nRemoveObserver", "(JJ)V", (void*)android_view_ThreadedRenderer_removeObserver},
- {"nCopySurfaceInto", "(Landroid/view/Surface;IIIIJ)I",
+ {"nCopySurfaceInto",
+ "(Landroid/view/Surface;IIIILandroid/graphics/HardwareRenderer$CopyRequest;)V",
(void*)android_view_ThreadedRenderer_copySurfaceInto},
{"nCreateHardwareBitmap", "(JII)Landroid/graphics/Bitmap;",
(void*)android_view_ThreadedRenderer_createHardwareBitmapFromRenderNode},
@@ -1042,6 +1074,11 @@ int register_android_view_ThreadedRenderer(JNIEnv* env) {
gFrameCompleteCallback.onFrameComplete =
GetMethodIDOrDie(env, frameCompleteClass, "onFrameComplete", "()V");
+ jclass copyRequest = FindClassOrDie(env, "android/graphics/HardwareRenderer$CopyRequest");
+ gCopyRequest.onCopyFinished = GetMethodIDOrDie(env, copyRequest, "onCopyFinished", "(I)V");
+ gCopyRequest.getDestinationBitmap =
+ GetMethodIDOrDie(env, copyRequest, "getDestinationBitmap", "(II)J");
+
void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
fromSurface = (ANW_fromSurface)dlsym(handle_, "ANativeWindow_fromSurface");
LOG_ALWAYS_FATAL_IF(fromSurface == nullptr,
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index b2ba15cbe526..40a0bac3762d 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -364,12 +364,13 @@ void RenderProxy::setForceDark(bool enable) {
mRenderThread.queue().post([this, enable]() { mContext->setForceDark(enable); });
}
-int RenderProxy::copySurfaceInto(ANativeWindow* window, int left, int top, int right, int bottom,
- SkBitmap* bitmap) {
+void RenderProxy::copySurfaceInto(ANativeWindow* window, std::shared_ptr&& request) {
auto& thread = RenderThread::getInstance();
- return static_cast(thread.queue().runSync([&]() -> auto {
- return thread.readback().copySurfaceInto(window, Rect(left, top, right, bottom), bitmap);
- }));
+ ANativeWindow_acquire(window);
+ thread.queue().post([&thread, window, request = std::move(request)] {
+ thread.readback().copySurfaceInto(window, request);
+ ANativeWindow_release(window);
+ });
}
void RenderProxy::prepareToDraw(Bitmap& bitmap) {
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index bbfeeac19d94..2a99a736180f 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -19,13 +19,14 @@
#include
#include
-#include
#include
+#include
#include
#include "../FrameMetricsObserver.h"
#include "../IContextFactory.h"
#include "ColorMode.h"
+#include "CopyRequest.h"
#include "DrawFrameTask.h"
#include "SwapBehavior.h"
#include "hwui/Bitmap.h"
@@ -137,8 +138,7 @@ public:
void removeFrameMetricsObserver(FrameMetricsObserver* observer);
void setForceDark(bool enable);
- static int copySurfaceInto(ANativeWindow* window, int left, int top, int right,
- int bottom, SkBitmap* bitmap);
+ static void copySurfaceInto(ANativeWindow* window, std::shared_ptr&& request);
static void prepareToDraw(Bitmap& bitmap);
static int copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap);
diff --git a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp
index 070a339a86a1..13a438199ae5 100644
--- a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp
@@ -25,17 +25,51 @@
class MagnifierAnimation;
+using Rect = android::uirenderer::Rect;
+
static TestScene::Registrar _Magnifier(TestScene::Info{
"magnifier", "A sample magnifier using Readback",
TestScene::simpleCreateScene});
+class BlockingCopyRequest : public CopyRequest {
+ sk_sp mDestination;
+ std::mutex mLock;
+ std::condition_variable mCondVar;
+ CopyResult mResult;
+
+public:
+ BlockingCopyRequest(::Rect rect, sk_sp bitmap)
+ : CopyRequest(rect), mDestination(bitmap) {}
+
+ virtual SkBitmap getDestinationBitmap(int srcWidth, int srcHeight) override {
+ SkBitmap bitmap;
+ mDestination->getSkBitmap(&bitmap);
+ return bitmap;
+ }
+
+ virtual void onCopyFinished(CopyResult result) override {
+ std::unique_lock _lock{mLock};
+ mResult = result;
+ mCondVar.notify_all();
+ }
+
+ CopyResult waitForResult() {
+ std::unique_lock _lock{mLock};
+ mCondVar.wait(_lock);
+ return mResult;
+ }
+};
+
class MagnifierAnimation : public TestScene {
public:
sp card;
sp zoomImageView;
+ sk_sp magnifier;
+ std::shared_ptr copyRequest;
void createContent(int width, int height, Canvas& canvas) override {
magnifier = TestUtils::createBitmap(200, 100);
+ setupCopyRequest();
SkBitmap temp;
magnifier->getSkBitmap(&temp);
temp.eraseColor(Color::White);
@@ -65,19 +99,20 @@ public:
canvas.enableZ(false);
}
+ void setupCopyRequest() {
+ constexpr int x = 90;
+ constexpr int y = 325;
+ copyRequest = std::make_shared(
+ ::Rect(x, y, x + magnifier->width(), y + magnifier->height()), magnifier);
+ }
+
void doFrame(int frameNr) override {
int curFrame = frameNr % 150;
card->mutateStagingProperties().setTranslationX(curFrame);
card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
if (renderTarget) {
- SkBitmap temp;
- magnifier->getSkBitmap(&temp);
- constexpr int x = 90;
- constexpr int y = 325;
- RenderProxy::copySurfaceInto(renderTarget.get(), x, y, x + magnifier->width(),
- y + magnifier->height(), &temp);
+ RenderProxy::copySurfaceInto(renderTarget.get(), copyRequest);
+ copyRequest->waitForResult();
}
}
-
- sk_sp magnifier;
};
diff --git a/libs/hwui/thread/WorkQueue.h b/libs/hwui/thread/WorkQueue.h
index 46b8bc07b432..f2751d2a6cc7 100644
--- a/libs/hwui/thread/WorkQueue.h
+++ b/libs/hwui/thread/WorkQueue.h
@@ -57,7 +57,7 @@ private:
public:
WorkQueue(std::function&& wakeFunc, std::mutex& lock)
- : mWakeFunc(move(wakeFunc)), mLock(lock) {}
+ : mWakeFunc(std::move(wakeFunc)), mLock(lock) {}
void process() {
auto now = clock::now();
--
cgit v1.2.3-59-g8ed1b
From c93103c9ebb356eba4a58b2729c42f41142bbc3b Mon Sep 17 00:00:00 2001
From: John Reck
Date: Thu, 4 Aug 2022 12:40:45 -0400
Subject: Remove Path's isSimplePath
The optimization has bugs and is of questionable value
given the current state of JNI. Just remove it.
Fixes: 149585703
Test: uirendering CTS, Path graphics test
Change-Id: I613e4854695ae6e4ad9ec8b17be44e856ed03860
---
.../src/android/graphics/PathOffsetTest.java | 91 --------------------
.../coretests/src/android/graphics/PathTest.java | 39 ---------
graphics/java/android/graphics/BaseCanvas.java | 6 +-
.../java/android/graphics/BaseRecordingCanvas.java | 6 +-
graphics/java/android/graphics/Path.java | 97 +---------------------
5 files changed, 5 insertions(+), 234 deletions(-)
delete mode 100644 core/tests/coretests/src/android/graphics/PathOffsetTest.java
delete mode 100644 core/tests/coretests/src/android/graphics/PathTest.java
(limited to 'graphics/java')
diff --git a/core/tests/coretests/src/android/graphics/PathOffsetTest.java b/core/tests/coretests/src/android/graphics/PathOffsetTest.java
deleted file mode 100644
index 6cc42f6a3efc..000000000000
--- a/core/tests/coretests/src/android/graphics/PathOffsetTest.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2008 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 static org.junit.Assert.assertTrue;
-
-import android.graphics.Bitmap.Config;
-import android.graphics.Path.Direction;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-public class PathOffsetTest {
-
- private static final int SQUARE = 10;
- private static final int WIDTH = 100;
- private static final int HEIGHT = 100;
- private static final int START_X = 10;
- private static final int START_Y = 20;
- private static final int OFFSET_X = 30;
- private static final int OFFSET_Y = 40;
-
- @Test
- @SmallTest
- public void testPathOffset() {
- Path actualPath = new Path();
- actualPath.addRect(START_X, START_Y, START_X + SQUARE, START_Y + SQUARE, Direction.CW);
- assertTrue(actualPath.isSimplePath);
- actualPath.offset(OFFSET_X, OFFSET_Y);
- assertTrue(actualPath.isSimplePath);
-
- Path expectedPath = new Path();
- expectedPath.addRect(START_X + OFFSET_X, START_Y + OFFSET_Y, START_X + OFFSET_X + SQUARE,
- START_Y + OFFSET_Y + SQUARE, Direction.CW);
-
- assertPaths(actualPath, expectedPath);
- }
-
- @Test
- @SmallTest
- public void testPathOffsetWithDestination() {
- Path initialPath = new Path();
- initialPath.addRect(START_X, START_Y, START_X + SQUARE, START_Y + SQUARE, Direction.CW);
- Path actualPath = new Path();
- assertTrue(initialPath.isSimplePath);
- assertTrue(actualPath.isSimplePath);
- initialPath.offset(OFFSET_X, OFFSET_Y, actualPath);
- assertTrue(actualPath.isSimplePath);
-
- Path expectedPath = new Path();
- expectedPath.addRect(START_X + OFFSET_X, START_Y + OFFSET_Y, START_X + OFFSET_X + SQUARE,
- START_Y + OFFSET_Y + SQUARE, Direction.CW);
-
- assertPaths(actualPath, expectedPath);
- }
-
- private static void assertPaths(Path actual, Path expected) {
- Bitmap actualBitmap = drawAndGetBitmap(actual);
- Bitmap expectedBitmap = drawAndGetBitmap(expected);
- assertTrue(actualBitmap.sameAs(expectedBitmap));
- }
-
- private static Bitmap drawAndGetBitmap(Path path) {
- Bitmap bitmap = Bitmap.createBitmap(WIDTH, HEIGHT, Config.ARGB_8888);
- bitmap.eraseColor(Color.BLACK);
- Paint paint = new Paint();
- paint.setColor(Color.RED);
- Canvas canvas = new Canvas(bitmap);
- canvas.drawPath(path, paint);
- return bitmap;
- }
-
-}
diff --git a/core/tests/coretests/src/android/graphics/PathTest.java b/core/tests/coretests/src/android/graphics/PathTest.java
deleted file mode 100644
index b50792ca6b38..000000000000
--- a/core/tests/coretests/src/android/graphics/PathTest.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2013 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 androidx.test.filters.SmallTest;
-
-import junit.framework.TestCase;
-
-public class PathTest extends TestCase {
-
- @SmallTest
- public void testResetPreservesFillType() throws Exception {
- Path path = new Path();
-
- final Path.FillType defaultFillType = path.getFillType();
- final Path.FillType fillType = Path.FillType.INVERSE_EVEN_ODD;
-
- // This test is only meaningful if it changes from the default.
- assertFalse(fillType.equals(defaultFillType));
-
- path.setFillType(fillType);
- path.reset();
- assertEquals(path.getFillType(), fillType);
- }
-}
diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java
index 425a37891afb..a8ab6d9e494e 100644
--- a/graphics/java/android/graphics/BaseCanvas.java
+++ b/graphics/java/android/graphics/BaseCanvas.java
@@ -330,11 +330,7 @@ public abstract class BaseCanvas {
public void drawPath(@NonNull Path path, @NonNull Paint paint) {
throwIfHasHwFeaturesInSwMode(paint);
- if (path.isSimplePath && path.rects != null) {
- nDrawRegion(mNativeCanvasWrapper, path.rects.mNativeRegion, paint.getNativeInstance());
- } else {
- nDrawPath(mNativeCanvasWrapper, path.readOnlyNI(), paint.getNativeInstance());
- }
+ nDrawPath(mNativeCanvasWrapper, path.readOnlyNI(), paint.getNativeInstance());
}
public void drawPoint(float x, float y, @NonNull Paint paint) {
diff --git a/graphics/java/android/graphics/BaseRecordingCanvas.java b/graphics/java/android/graphics/BaseRecordingCanvas.java
index a998ba870f74..d06f665631cc 100644
--- a/graphics/java/android/graphics/BaseRecordingCanvas.java
+++ b/graphics/java/android/graphics/BaseRecordingCanvas.java
@@ -286,11 +286,7 @@ public class BaseRecordingCanvas extends Canvas {
@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());
- }
+ nDrawPath(mNativeCanvasWrapper, path.readOnlyNI(), paint.getNativeInstance());
}
@Override
diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java
index e5ef10d1d555..afcaabebb9fb 100644
--- a/graphics/java/android/graphics/Path.java
+++ b/graphics/java/android/graphics/Path.java
@@ -20,8 +20,6 @@ import android.annotation.FloatRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
-import android.compat.annotation.UnsupportedAppUsage;
-import android.os.Build;
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
@@ -46,18 +44,6 @@ public class Path {
*/
public final long mNativePath;
- /**
- * @hide
- */
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public boolean isSimplePath = true;
- /**
- * @hide
- */
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public Region rects;
- private Direction mLastDirection = null;
-
/**
* Create an empty path
*/
@@ -72,15 +58,7 @@ public class Path {
* @param src The path to copy from when initializing the new path
*/
public Path(@Nullable Path src) {
- long valNative = 0;
- if (src != null) {
- valNative = src.mNativePath;
- isSimplePath = src.isSimplePath;
- if (src.rects != null) {
- rects = new Region(src.rects);
- }
- }
- mNativePath = nInit(valNative);
+ mNativePath = nInit(src != null ? src.mNativePath : 0);
sRegistry.registerNativeAllocation(this, mNativePath);
}
@@ -89,9 +67,6 @@ public class Path {
* This does NOT change the fill-type setting.
*/
public void reset() {
- isSimplePath = true;
- mLastDirection = null;
- if (rects != null) rects.setEmpty();
// We promised not to change this, so preserve it around the native
// call, which does now reset fill type.
final FillType fillType = getFillType();
@@ -104,9 +79,6 @@ public class Path {
* keeps the internal data structure for faster reuse.
*/
public void rewind() {
- isSimplePath = true;
- mLastDirection = null;
- if (rects != null) rects.setEmpty();
nRewind(mNativePath);
}
@@ -116,19 +88,7 @@ public class Path {
if (this == src) {
return;
}
- isSimplePath = src.isSimplePath;
nSet(mNativePath, src.mNativePath);
- if (!isSimplePath) {
- return;
- }
-
- if (rects != null && src.rects != null) {
- rects.set(src.rects);
- } else if (rects != null && src.rects == null) {
- rects.setEmpty();
- } else if (src.rects != null) {
- rects = new Region(src.rects);
- }
}
/**
@@ -192,12 +152,7 @@ public class Path {
* @see #op(Path, android.graphics.Path.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;
- return true;
- }
- return false;
+ return nOp(path1.mNativePath, path2.mNativePath, op.ordinal(), this.mNativePath);
}
/**
@@ -378,7 +333,6 @@ public class Path {
* @param y The y-coordinate of the end of a line
*/
public void lineTo(float x, float y) {
- isSimplePath = false;
nLineTo(mNativePath, x, y);
}
@@ -393,7 +347,6 @@ public class Path {
* this contour, to specify a line
*/
public void rLineTo(float dx, float dy) {
- isSimplePath = false;
nRLineTo(mNativePath, dx, dy);
}
@@ -408,7 +361,6 @@ public class Path {
* @param y2 The y-coordinate of the end point on a quadratic curve
*/
public void quadTo(float x1, float y1, float x2, float y2) {
- isSimplePath = false;
nQuadTo(mNativePath, x1, y1, x2, y2);
}
@@ -427,7 +379,6 @@ public class Path {
* this contour, for the end point of a quadratic curve
*/
public void rQuadTo(float dx1, float dy1, float dx2, float dy2) {
- isSimplePath = false;
nRQuadTo(mNativePath, dx1, dy1, dx2, dy2);
}
@@ -445,7 +396,6 @@ public class Path {
*/
public void cubicTo(float x1, float y1, float x2, float y2,
float x3, float y3) {
- isSimplePath = false;
nCubicTo(mNativePath, x1, y1, x2, y2, x3, y3);
}
@@ -456,7 +406,6 @@ public class Path {
*/
public void rCubicTo(float x1, float y1, float x2, float y2,
float x3, float y3) {
- isSimplePath = false;
nRCubicTo(mNativePath, x1, y1, x2, y2, x3, y3);
}
@@ -507,7 +456,6 @@ public class Path {
*/
public void arcTo(float left, float top, float right, float bottom, float startAngle,
float sweepAngle, boolean forceMoveTo) {
- isSimplePath = false;
nArcTo(mNativePath, left, top, right, bottom, startAngle, sweepAngle, forceMoveTo);
}
@@ -516,7 +464,6 @@ public class Path {
* first point of the contour, a line segment is automatically added.
*/
public void close() {
- isSimplePath = false;
nClose(mNativePath);
}
@@ -536,18 +483,6 @@ public class Path {
final int nativeInt;
}
- private void detectSimplePath(float left, float top, float right, float bottom, Direction dir) {
- if (mLastDirection == null) {
- mLastDirection = dir;
- }
- if (mLastDirection != dir) {
- isSimplePath = false;
- } else {
- if (rects == null) rects = new Region();
- rects.op((int) left, (int) top, (int) right, (int) bottom, Region.Op.UNION);
- }
- }
-
/**
* Add a closed rectangle contour to the path
*
@@ -568,7 +503,6 @@ public class Path {
* @param dir The direction to wind the rectangle's contour
*/
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);
}
@@ -588,7 +522,6 @@ public class Path {
* @param dir The direction to wind the oval's contour
*/
public void addOval(float left, float top, float right, float bottom, @NonNull Direction dir) {
- isSimplePath = false;
nAddOval(mNativePath, left, top, right, bottom, dir.nativeInt);
}
@@ -601,7 +534,6 @@ public class Path {
* @param dir The direction to wind the circle's contour
*/
public void addCircle(float x, float y, float radius, @NonNull Direction dir) {
- isSimplePath = false;
nAddCircle(mNativePath, x, y, radius, dir.nativeInt);
}
@@ -624,7 +556,6 @@ public class Path {
*/
public void addArc(float left, float top, float right, float bottom, float startAngle,
float sweepAngle) {
- isSimplePath = false;
nAddArc(mNativePath, left, top, right, bottom, startAngle, sweepAngle);
}
@@ -649,7 +580,6 @@ public class Path {
*/
public void addRoundRect(float left, float top, float right, float bottom, float rx, float ry,
@NonNull Direction dir) {
- isSimplePath = false;
nAddRoundRect(mNativePath, left, top, right, bottom, rx, ry, dir.nativeInt);
}
@@ -682,7 +612,6 @@ public class Path {
if (radii.length < 8) {
throw new ArrayIndexOutOfBoundsException("radii[] needs 8 values");
}
- isSimplePath = false;
nAddRoundRect(mNativePath, left, top, right, bottom, radii, dir.nativeInt);
}
@@ -693,7 +622,6 @@ public class Path {
* @param dx The amount to translate the path in X as it is added
*/
public void addPath(@NonNull Path src, float dx, float dy) {
- isSimplePath = false;
nAddPath(mNativePath, src.mNativePath, dx, dy);
}
@@ -703,7 +631,6 @@ public class Path {
* @param src The path that is appended to the current path
*/
public void addPath(@NonNull Path src) {
- isSimplePath = false;
nAddPath(mNativePath, src.mNativePath);
}
@@ -713,7 +640,6 @@ public class Path {
* @param src The path to add as a new contour
*/
public void addPath(@NonNull Path src, @NonNull Matrix matrix) {
- if (!src.isSimplePath) isSimplePath = false;
nAddPath(mNativePath, src.mNativePath, matrix.ni());
}
@@ -741,15 +667,6 @@ public class Path {
* @param dy The amount in the Y direction to offset the entire path
*/
public void offset(float dx, float dy) {
- if (isSimplePath && rects == null) {
- // nothing to offset
- return;
- }
- if (isSimplePath && dx == Math.rint(dx) && dy == Math.rint(dy)) {
- rects.translate((int) dx, (int) dy);
- } else {
- isSimplePath = false;
- }
nOffset(mNativePath, dx, dy);
}
@@ -760,7 +677,6 @@ public class Path {
* @param dy The new Y coordinate for the last point
*/
public void setLastPoint(float dx, float dy) {
- isSimplePath = false;
nSetLastPoint(mNativePath, dx, dy);
}
@@ -773,12 +689,7 @@ public class Path {
* then the the original path is modified
*/
public void transform(@NonNull Matrix matrix, @Nullable Path dst) {
- long dstNative = 0;
- if (dst != null) {
- dst.isSimplePath = false;
- dstNative = dst.mNativePath;
- }
- nTransform(mNativePath, matrix.ni(), dstNative);
+ nTransform(mNativePath, matrix.ni(), dst != null ? dst.mNativePath : 0);
}
/**
@@ -787,7 +698,6 @@ public class Path {
* @param matrix The matrix to apply to the path
*/
public void transform(@NonNull Matrix matrix) {
- isSimplePath = false;
nTransform(mNativePath, matrix.ni());
}
@@ -797,7 +707,6 @@ public class Path {
}
final long mutateNI() {
- isSimplePath = false;
return mNativePath;
}
--
cgit v1.2.3-59-g8ed1b
From 29727ab34235335d65529880f2672605d04dff74 Mon Sep 17 00:00:00 2001
From: Kohsuke Yatoh
Date: Thu, 4 Aug 2022 06:19:19 +0000
Subject: Remove unused Typeface.getFallback().
The usage was removed in
commit d5ecababb4c65db2c7f7fa2d7e5b8ddbbae94cf1
Bug: 174672300
Test: m -j
Change-Id: Ie7194d749c14a2c1b5c786bf4bce97bd0eb3a29e
---
graphics/java/android/graphics/Typeface.java | 17 -----------------
libs/hwui/jni/Typeface.cpp | 14 --------------
2 files changed, 31 deletions(-)
(limited to 'graphics/java')
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 8ae0d3d48d6d..25e1922fc38e 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -1557,16 +1557,6 @@ public class Typeface {
return Arrays.binarySearch(mSupportedAxes, axis) >= 0;
}
- /** @hide */
- public List getFallback() {
- ArrayList families = new ArrayList<>();
- int familySize = nativeGetFamilySize(native_instance);
- for (int i = 0; i < familySize; ++i) {
- families.add(new FontFamily(nativeGetFamily(native_instance, i)));
- }
- return families;
- }
-
private static native long nativeCreateFromTypeface(long native_instance, int style);
private static native long nativeCreateFromTypefaceWithExactStyle(
long native_instance, int weight, boolean italic);
@@ -1592,13 +1582,6 @@ public class Typeface {
@CriticalNative
private static native long nativeGetReleaseFunc();
- @CriticalNative
- private static native int nativeGetFamilySize(long naitvePtr);
-
- @CriticalNative
- private static native long nativeGetFamily(long nativePtr, int index);
-
-
private static native void nativeRegisterGenericFamily(String str, long nativePtr);
private static native int nativeWriteTypefaces(
diff --git a/libs/hwui/jni/Typeface.cpp b/libs/hwui/jni/Typeface.cpp
index 6a7119b821a2..f5ed5689e4e8 100644
--- a/libs/hwui/jni/Typeface.cpp
+++ b/libs/hwui/jni/Typeface.cpp
@@ -380,18 +380,6 @@ static void Typeface_forceSetStaticFinalField(JNIEnv *env, jclass cls, jstring f
env->SetStaticObjectField(cls, fid, typeface);
}
-// Critical Native
-static jint Typeface_getFamilySize(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle) {
- return toTypeface(faceHandle)->fFontCollection->getFamilyCount();
-}
-
-// Critical Native
-static jlong Typeface_getFamily(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle, jint index) {
- std::shared_ptr family =
- toTypeface(faceHandle)->fFontCollection->getFamilyAt(index);
- return reinterpret_cast(new FontFamilyWrapper(std::move(family)));
-}
-
// Regular JNI
static void Typeface_warmUpCache(JNIEnv* env, jobject, jstring jFilePath) {
ScopedUtfChars filePath(env, jFilePath);
@@ -431,8 +419,6 @@ static const JNINativeMethod gTypefaceMethods[] = {
{"nativeReadTypefaces", "(Ljava/nio/ByteBuffer;I)[J", (void*)Typeface_readTypefaces},
{"nativeForceSetStaticFinalField", "(Ljava/lang/String;Landroid/graphics/Typeface;)V",
(void*)Typeface_forceSetStaticFinalField},
- {"nativeGetFamilySize", "(J)I", (void*)Typeface_getFamilySize},
- {"nativeGetFamily", "(JI)J", (void*)Typeface_getFamily},
{"nativeWarmUpCache", "(Ljava/lang/String;)V", (void*)Typeface_warmUpCache},
{"nativeAddFontCollections", "(J)V", (void*)Typeface_addFontCollection},
{"nativeRegisterLocaleList", "(Ljava/lang/String;)V", (void*)Typeface_registerLocaleList},
--
cgit v1.2.3-59-g8ed1b
From f81fbd3eb08adfbf10d40cdff6b87584e1adc131 Mon Sep 17 00:00:00 2001
From: joneckenrode
Date: Fri, 12 Aug 2022 17:58:25 +0000
Subject: docs: Edited API documentation related to line-break and line-break
word styles.
Staged to:
https://android.devsite.corp.google.com/reference/android/graphics/text/LineBreakConfig
https://android.devsite.corp.google.com/reference/android/graphics/text/LineBreakConfig.Builder
https://android.devsite.corp.google.com/reference/android/widget/TextView
https://android.devsite.corp.google.com/reference/android/R.attr
https://android.devsite.corp.google.com/reference/kotlin/android/R.attr
Test: Built API docs
Change-Id: I06fdf27ba94d806753a1dcc99873dff24cdae626
---
core/java/android/widget/TextView.java | 63 +++++++++++--------
core/res/res/values/attrs.xml | 16 ++---
.../android/graphics/text/LineBreakConfig.java | 72 ++++++++++++----------
3 files changed, 86 insertions(+), 65 deletions(-)
(limited to 'graphics/java')
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 3f87ec2beae5..c6ac0382fa32 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -4887,20 +4887,28 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
- * Set the line break style for text wrapping.
+ * Sets the line-break style for text wrapping.
*
- * The line break style to indicates the line break strategies can be used when
- * calculating the text wrapping. The line break style affects rule-based breaking. It
- * specifies the strictness of line-breaking rules.
- * There are several types for the line break style:
- * {@link LineBreakConfig#LINE_BREAK_STYLE_LOOSE},
- * {@link LineBreakConfig#LINE_BREAK_STYLE_NORMAL} and
- * {@link LineBreakConfig#LINE_BREAK_STYLE_STRICT}. The default values of the line break style
- * is {@link LineBreakConfig#LINE_BREAK_STYLE_NONE}, indicating no breaking rule is specified.
- * See
- * the line-break property
+ * Line-break style specifies the line-break strategies that can be used
+ * for text wrapping. The line-break style affects rule-based line breaking
+ * by specifying the strictness of line-breaking rules.
*
- * @param lineBreakStyle the line break style for the text.
+ * The following are types of line-break styles:
+ *
+ * - {@link LineBreakConfig#LINE_BREAK_STYLE_LOOSE}
+ * - {@link LineBreakConfig#LINE_BREAK_STYLE_NORMAL}
+ * - {@link LineBreakConfig#LINE_BREAK_STYLE_STRICT}
+ *
+ *
+ * The default line-break style is
+ * {@link LineBreakConfig#LINE_BREAK_STYLE_NONE}, which specifies that no
+ * line-breaking rules are used.
+ *
+ * See the
+ *
+ * line-break property for more information.
+ *
+ * @param lineBreakStyle The line break style for the text.
*/
public void setLineBreakStyle(@LineBreakConfig.LineBreakStyle int lineBreakStyle) {
if (mLineBreakStyle != lineBreakStyle) {
@@ -4914,17 +4922,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
- * Set the line break word style for text wrapping.
+ * Sets the line-break word style for text wrapping.
+ *
+ * The line-break word style affects dictionary-based line breaking by
+ * providing phrase-based line-breaking opportunities. Use
+ * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_PHRASE} to specify
+ * phrase-based line breaking.
+ *
+ * The default line-break word style is
+ * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_NONE}, which specifies that
+ * no line-breaking word style is used.
*
- * The line break word style affects dictionary-based breaking and provide phrase-based
- * breaking opportunities. The type for the line break word style is
- * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_PHRASE}. The default values of the line break
- * word style is {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_NONE}, indicating no breaking rule
- * is specified.
- * See
- * the word-break property
+ * See the
+ *
+ * word-break property for more information.
*
- * @param lineBreakWordStyle the line break word style for the tet
+ * @param lineBreakWordStyle The line break word style for the text.
*/
public void setLineBreakWordStyle(@LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) {
mUserSpeficiedLineBreakwordStyle = true;
@@ -4939,18 +4952,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
- * Get the current line break style for text wrapping.
+ * Gets the current line-break style for text wrapping.
*
- * @return the current line break style to be used for text wrapping.
+ * @return The line-break style to be used for text wrapping.
*/
public @LineBreakConfig.LineBreakStyle int getLineBreakStyle() {
return mLineBreakStyle;
}
/**
- * Get the current line word break style for text wrapping.
+ * Gets the current line-break word style for text wrapping.
*
- * @return the current line break word style to be used for text wrapping.
+ * @return The line-break word style to be used for text wrapping.
*/
public @LineBreakConfig.LineBreakWordStyle int getLineBreakWordStyle() {
return mLineBreakWordStyle;
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index d43a6c59a477..c7153fcf8609 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5542,22 +5542,22 @@
ignores some hyphen character related typographic features, e.g. kerning. -->
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Copyright (C) 2017 The Android Open Source Project
+
+
+ Sample Font
+
+
+ Regular
+
+
+ Sample Font
+
+
+ fallback
+
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ 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.
+
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/tests/coretests/assets/fonts/fallback_capital.ttf b/core/tests/coretests/assets/fonts/fallback_capital.ttf
new file mode 100644
index 000000000000..073761f08341
Binary files /dev/null and b/core/tests/coretests/assets/fonts/fallback_capital.ttf differ
diff --git a/core/tests/coretests/assets/fonts/fallback_capital.ttx b/core/tests/coretests/assets/fonts/fallback_capital.ttx
new file mode 100644
index 000000000000..710c95e091a7
--- /dev/null
+++ b/core/tests/coretests/assets/fonts/fallback_capital.ttx
@@ -0,0 +1,205 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Copyright (C) 2017 The Android Open Source Project
+
+
+ Sample Font
+
+
+ Regular
+
+
+ Sample Font
+
+
+ fallback_capital
+
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ 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.
+
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/tests/coretests/src/android/graphics/FontListParserTest.java b/core/tests/coretests/src/android/graphics/FontListParserTest.java
index 479e52aab029..d46f7625b596 100644
--- a/core/tests/coretests/src/android/graphics/FontListParserTest.java
+++ b/core/tests/coretests/src/android/graphics/FontListParserTest.java
@@ -47,6 +47,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
@SmallTest
@@ -59,14 +60,12 @@ public final class FontListParserTest {
+ ""
+ " test.ttf"
+ "";
- FontConfig.FontFamily expected = new FontConfig.FontFamily(
- Arrays.asList(
- new FontConfig.Font(new File("test.ttf"), null, "test",
- new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
- 0, "", null)),
- "sans-serif", LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT);
-
- FontConfig.FontFamily family = readFamily(xml);
+ FontConfig.NamedFamilyList expected = new FontConfig.NamedFamilyList(
+ Collections.singletonList(new FontConfig.FontFamily(
+ Arrays.asList(new FontConfig.Font(new File("test.ttf"), null, "test",
+ new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null)),
+ LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)), "sans-serif");
+ FontConfig.NamedFamilyList family = readNamedFamily(xml);
assertThat(family).isEqualTo(expected);
}
@@ -85,7 +84,7 @@ public final class FontListParserTest {
new FontConfig.Font(new File("test.ttf"), null, "test",
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
0, "", "serif")),
- null, LocaleList.forLanguageTags("en"), VARIANT_DEFAULT);
+ LocaleList.forLanguageTags("en"), VARIANT_DEFAULT);
FontConfig.FontFamily family = readFamily(xml);
assertThat(family).isEqualTo(expected);
@@ -102,7 +101,7 @@ public final class FontListParserTest {
new FontConfig.Font(new File("test.ttf"), null, "test",
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
0, "", null)),
- null, LocaleList.forLanguageTags("en"), VARIANT_COMPACT);
+ LocaleList.forLanguageTags("en"), VARIANT_COMPACT);
FontConfig.FontFamily family = readFamily(xml);
assertThat(family).isEqualTo(expected);
@@ -119,7 +118,7 @@ public final class FontListParserTest {
new FontConfig.Font(new File("test.ttf"), null, "test",
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
0, "", null)),
- null, LocaleList.forLanguageTags("en"), VARIANT_ELEGANT);
+ LocaleList.forLanguageTags("en"), VARIANT_ELEGANT);
FontConfig.FontFamily family = readFamily(xml);
assertThat(family).isEqualTo(expected);
@@ -133,19 +132,16 @@ public final class FontListParserTest {
+ " weight.ttf"
+ " italic.ttf"
+ "";
- FontConfig.FontFamily expected = new FontConfig.FontFamily(
- Arrays.asList(
- new FontConfig.Font(new File("normal.ttf"), null, "test",
- new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
- 0, "", null),
- new FontConfig.Font(new File("weight.ttf"), null, "test",
- new FontStyle(100, FONT_SLANT_UPRIGHT),
- 0, "", null),
- new FontConfig.Font(new File("italic.ttf"), null, "test",
- new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_ITALIC),
- 0, "", null)),
- "sans-serif", LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT);
- FontConfig.FontFamily family = readFamily(xml);
+ FontConfig.NamedFamilyList expected = new FontConfig.NamedFamilyList(
+ Collections.singletonList(new FontConfig.FontFamily(Arrays.asList(
+ new FontConfig.Font(new File("normal.ttf"), null, "test",
+ new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null),
+ new FontConfig.Font(new File("weight.ttf"), null, "test",
+ new FontStyle(100, FONT_SLANT_UPRIGHT), 0, "", null),
+ new FontConfig.Font(new File("italic.ttf"), null, "test",
+ new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_ITALIC), 0, "", null)),
+ LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)), "sans-serif");
+ FontConfig.NamedFamilyList family = readNamedFamily(xml);
assertThat(family).isEqualTo(expected);
}
@@ -162,16 +158,17 @@ public final class FontListParserTest {
+ " "
+ " "
+ "";
- FontConfig.FontFamily expected = new FontConfig.FontFamily(
- Arrays.asList(
+ FontConfig.NamedFamilyList expected = new FontConfig.NamedFamilyList(
+ Collections.singletonList(new FontConfig.FontFamily(Arrays.asList(
new FontConfig.Font(new File("test-VF.ttf"), null, "test",
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
0, "'wdth' 100.0,'wght' 200.0", null),
new FontConfig.Font(new File("test-VF.ttf"), null, "test",
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
0, "'wdth' 400.0,'wght' 700.0", null)),
- "sans-serif", LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT);
- FontConfig.FontFamily family = readFamily(xml);
+ LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)),
+ "sans-serif");
+ FontConfig.NamedFamilyList family = readNamedFamily(xml);
assertThat(family).isEqualTo(expected);
}
@@ -182,16 +179,17 @@ public final class FontListParserTest {
+ " test.ttc"
+ " test.ttc"
+ "";
- FontConfig.FontFamily expected = new FontConfig.FontFamily(
- Arrays.asList(
+ FontConfig.NamedFamilyList expected = new FontConfig.NamedFamilyList(
+ Collections.singletonList(new FontConfig.FontFamily(Arrays.asList(
new FontConfig.Font(new File("test.ttc"), null, "test",
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
0, "", null),
new FontConfig.Font(new File("test.ttc"), null, "test",
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
1, "", null)),
- "sans-serif", LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT);
- FontConfig.FontFamily family = readFamily(xml);
+ LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)),
+ "sans-serif");
+ FontConfig.NamedFamilyList family = readNamedFamily(xml);
assertThat(family).isEqualTo(expected);
}
@@ -202,16 +200,14 @@ public final class FontListParserTest {
+ " test.ttc"
+ " test.ttc"
+ "";
- FontConfig.FontFamily expected = new FontConfig.FontFamily(
- Arrays.asList(
- new FontConfig.Font(new File("test.ttc"), null, "foo",
- new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
- 0, "", null),
- new FontConfig.Font(new File("test.ttc"), null, "test",
- new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
- 1, "", null)),
- "sans-serif", LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT);
- FontConfig.FontFamily family = readFamily(xml);
+ FontConfig.NamedFamilyList expected = new FontConfig.NamedFamilyList(
+ Collections.singletonList(new FontConfig.FontFamily(Arrays.asList(
+ new FontConfig.Font(new File("test.ttc"), null, "foo",
+ new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null),
+ new FontConfig.Font(new File("test.ttc"), null, "test",
+ new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 1, "", null)),
+ LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)), "sans-serif");
+ FontConfig.NamedFamilyList family = readNamedFamily(xml);
assertThat(family).isEqualTo(expected);
}
@@ -396,4 +392,14 @@ public final class FontListParserTest {
parser.nextTag();
return FontListParser.readFamily(parser, "", null, true);
}
+
+ private FontConfig.NamedFamilyList readNamedFamily(String xml)
+ throws IOException, XmlPullParserException {
+ ByteArrayInputStream buffer = new ByteArrayInputStream(
+ xml.getBytes(StandardCharsets.UTF_8));
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(buffer, "UTF-8");
+ parser.nextTag();
+ return FontListParser.readNamedFamily(parser, "", null, true);
+ }
}
diff --git a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
index 3df2e90241ff..a8a5059eea20 100644
--- a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
+++ b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
@@ -28,6 +28,8 @@ import android.content.res.AssetManager;
import android.graphics.fonts.FontCustomizationParser;
import android.graphics.fonts.FontFamily;
import android.graphics.fonts.SystemFonts;
+import android.graphics.text.PositionedGlyphs;
+import android.graphics.text.TextRunShaper;
import android.text.FontConfig;
import android.util.ArrayMap;
@@ -64,6 +66,8 @@ public class TypefaceSystemFallbackTest {
"b3em.ttf", // Supports "a","b","c". The width of "b" is 3em, others are 1em.
"c3em.ttf", // Supports "a","b","c". The width of "c" is 3em, others are 1em.
"all2em.ttf", // Supports "a,","b","c". All of them have the same width of 2em.
+ "fallback.ttf", // SUpports all small alphabets.
+ "fallback_capital.ttf", // SUpports all capital alphabets.
"no_coverage.ttf", // This font doesn't support any characters.
};
private static final String TEST_FONTS_XML;
@@ -165,7 +169,10 @@ public class TypefaceSystemFallbackTest {
Map updatableFontMap = new HashMap<>();
for (File file : new File(TEST_UPDATABLE_FONT_DIR).listFiles()) {
- updatableFontMap.put(file.getName(), file);
+ final String fileName = file.getName();
+ final int periodIndex = fileName.lastIndexOf(".");
+ final String psName = fileName.substring(0, periodIndex);
+ updatableFontMap.put(psName, file);
}
FontConfig fontConfig;
@@ -710,6 +717,47 @@ public class TypefaceSystemFallbackTest {
assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0.0f);
}
+ private String getFontName(Paint paint, String text) {
+ PositionedGlyphs glyphs = TextRunShaper.shapeTextRun(
+ text, 0, text.length(), 0, text.length(), 0f, 0f, false, paint);
+ assertEquals(1, glyphs.glyphCount());
+ return glyphs.getFont(0).getFile().getName();
+ }
+
+ @Test
+ public void testBuildSystemFallback__Customization_new_named_familyList() {
+ final String xml = ""
+ + ""
+ + " "
+ + " fallback_capital.ttf"
+ + " "
+ + "";
+ final String oemXml = ""
+ + ""
+ + " "
+ + " "
+ + " b3em.ttf"
+ + " "
+ + " "
+ + " fallback.ttf"
+ + " "
+ + " "
+ + "";
+ final ArrayMap fontMap = new ArrayMap<>();
+ final ArrayMap fallbackMap = new ArrayMap<>();
+
+ buildSystemFallback(xml, oemXml, fontMap, fallbackMap);
+
+ final Paint paint = new Paint();
+
+ Typeface testTypeface = fontMap.get("google-sans");
+ assertNotNull(testTypeface);
+ paint.setTypeface(testTypeface);
+ assertEquals("b3em.ttf", getFontName(paint, "a"));
+ assertEquals("fallback.ttf", getFontName(paint, "x"));
+ assertEquals("fallback_capital.ttf", getFontName(paint, "A"));
+ }
+
@Test
public void testBuildSystemFallback__Customization_new_named_family_override() {
final String xml = ""
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index 4bb16c6b8186..674246acafef 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -16,6 +16,8 @@
package android.graphics;
+import static android.text.FontConfig.NamedFamilyList;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
@@ -36,6 +38,7 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -46,6 +49,7 @@ import java.util.regex.Pattern;
* @hide
*/
public class FontListParser {
+ private static final String TAG = "FontListParser";
// XML constants for FontFamily.
private static final String ATTR_NAME = "name";
@@ -148,27 +152,60 @@ public class FontListParser {
boolean allowNonExistingFile)
throws XmlPullParserException, IOException {
List families = new ArrayList<>();
+ List resultNamedFamilies = new ArrayList<>();
List aliases = new ArrayList<>(customization.getAdditionalAliases());
- Map oemNamedFamilies =
+ Map oemNamedFamilies =
customization.getAdditionalNamedFamilies();
+ boolean firstFamily = true;
parser.require(XmlPullParser.START_TAG, null, "familyset");
while (keepReading(parser)) {
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
String tag = parser.getName();
if (tag.equals("family")) {
- FontConfig.FontFamily family = readFamily(parser, fontDir, updatableFontMap,
- allowNonExistingFile);
- if (family == null) {
+ final String name = parser.getAttributeValue(null, "name");
+ if (name == null) {
+ FontConfig.FontFamily family = readFamily(parser, fontDir, updatableFontMap,
+ allowNonExistingFile);
+ if (family == null) {
+ continue;
+ }
+ families.add(family);
+
+ } else {
+ FontConfig.NamedFamilyList namedFamilyList = readNamedFamily(
+ parser, fontDir, updatableFontMap, allowNonExistingFile);
+ if (namedFamilyList == null) {
+ continue;
+ }
+ if (!oemNamedFamilies.containsKey(name)) {
+ // The OEM customization overrides system named family. Skip if OEM
+ // customization XML defines the same named family.
+ resultNamedFamilies.add(namedFamilyList);
+ }
+ if (firstFamily) {
+ // The first font family is used as a fallback family as well.
+ families.addAll(namedFamilyList.getFamilies());
+ }
+ }
+ firstFamily = false;
+ } else if (tag.equals("family-list")) {
+ FontConfig.NamedFamilyList namedFamilyList = readNamedFamilyList(
+ parser, fontDir, updatableFontMap, allowNonExistingFile);
+ if (namedFamilyList == null) {
continue;
}
- String name = family.getName();
- if (name == null || !oemNamedFamilies.containsKey(name)) {
+ if (!oemNamedFamilies.containsKey(namedFamilyList.getName())) {
// The OEM customization overrides system named family. Skip if OEM
// customization XML defines the same named family.
- families.add(family);
+ resultNamedFamilies.add(namedFamilyList);
}
+ if (firstFamily) {
+ // The first font family is used as a fallback family as well.
+ families.addAll(namedFamilyList.getFamilies());
+ }
+ firstFamily = false;
} else if (tag.equals("alias")) {
aliases.add(readAlias(parser));
} else {
@@ -176,12 +213,12 @@ public class FontListParser {
}
}
- families.addAll(oemNamedFamilies.values());
+ resultNamedFamilies.addAll(oemNamedFamilies.values());
// Filters aliases that point to non-existing families.
Set namedFamilies = new ArraySet<>();
- for (int i = 0; i < families.size(); ++i) {
- String name = families.get(i).getName();
+ for (int i = 0; i < resultNamedFamilies.size(); ++i) {
+ String name = resultNamedFamilies.get(i).getName();
if (name != null) {
namedFamilies.add(name);
}
@@ -194,7 +231,8 @@ public class FontListParser {
}
}
- return new FontConfig(families, filtered, lastModifiedDate, configVersion);
+ return new FontConfig(families, filtered, resultNamedFamilies, lastModifiedDate,
+ configVersion);
}
private static boolean keepReading(XmlPullParser parser)
@@ -215,7 +253,6 @@ public class FontListParser {
public static @Nullable FontConfig.FontFamily readFamily(XmlPullParser parser, String fontDir,
@Nullable Map updatableFontMap, boolean allowNonExistingFile)
throws XmlPullParserException, IOException {
- final String name = parser.getAttributeValue(null, "name");
final String lang = parser.getAttributeValue("", "lang");
final String variant = parser.getAttributeValue(null, "variant");
final String ignore = parser.getAttributeValue(null, "ignore");
@@ -246,7 +283,68 @@ public class FontListParser {
if (skip || fonts.isEmpty()) {
return null;
}
- return new FontConfig.FontFamily(fonts, name, LocaleList.forLanguageTags(lang), intVariant);
+ return new FontConfig.FontFamily(fonts, LocaleList.forLanguageTags(lang), intVariant);
+ }
+
+ private static void throwIfAttributeExists(String attrName, XmlPullParser parser) {
+ if (parser.getAttributeValue(null, attrName) != null) {
+ throw new IllegalArgumentException(attrName + " cannot be used in FontFamily inside "
+ + " family or family-list with name attribute.");
+ }
+ }
+
+ /**
+ * Read a font family with name attribute as a single element family-list element.
+ */
+ public static @Nullable FontConfig.NamedFamilyList readNamedFamily(
+ @NonNull XmlPullParser parser, @NonNull String fontDir,
+ @Nullable Map updatableFontMap, boolean allowNonExistingFile)
+ throws XmlPullParserException, IOException {
+ final String name = parser.getAttributeValue(null, "name");
+ throwIfAttributeExists("lang", parser);
+ throwIfAttributeExists("variant", parser);
+ throwIfAttributeExists("ignore", parser);
+
+ final FontConfig.FontFamily family = readFamily(parser, fontDir, updatableFontMap,
+ allowNonExistingFile);
+ if (family == null) {
+ return null;
+ }
+ return new NamedFamilyList(Collections.singletonList(family), name);
+ }
+
+ /**
+ * Read a family-list element
+ */
+ public static @Nullable FontConfig.NamedFamilyList readNamedFamilyList(
+ @NonNull XmlPullParser parser, @NonNull String fontDir,
+ @Nullable Map updatableFontMap, boolean allowNonExistingFile)
+ throws XmlPullParserException, IOException {
+ final String name = parser.getAttributeValue(null, "name");
+ final List familyList = new ArrayList<>();
+ while (keepReading(parser)) {
+ if (parser.getEventType() != XmlPullParser.START_TAG) continue;
+ final String tag = parser.getName();
+ if (tag.equals("family")) {
+ throwIfAttributeExists("name", parser);
+ throwIfAttributeExists("lang", parser);
+ throwIfAttributeExists("variant", parser);
+ throwIfAttributeExists("ignore", parser);
+
+ final FontConfig.FontFamily family = readFamily(parser, fontDir, updatableFontMap,
+ allowNonExistingFile);
+ if (family != null) {
+ familyList.add(family);
+ }
+ } else {
+ skip(parser);
+ }
+ }
+
+ if (familyList.isEmpty()) {
+ return null;
+ }
+ return new FontConfig.NamedFamilyList(familyList, name);
}
/** Matches leading and trailing XML whitespace. */
diff --git a/graphics/java/android/graphics/fonts/FontCustomizationParser.java b/graphics/java/android/graphics/fonts/FontCustomizationParser.java
index df47f73eb04a..b458dd9021d0 100644
--- a/graphics/java/android/graphics/fonts/FontCustomizationParser.java
+++ b/graphics/java/android/graphics/fonts/FontCustomizationParser.java
@@ -17,7 +17,7 @@
package android.graphics.fonts;
import static android.text.FontConfig.Alias;
-import static android.text.FontConfig.FontFamily;
+import static android.text.FontConfig.NamedFamilyList;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -42,11 +42,14 @@ import java.util.Map;
* @hide
*/
public class FontCustomizationParser {
+ private static final String TAG = "FontCustomizationParser";
+
/**
* Represents a customization XML
*/
public static class Result {
- private final Map mAdditionalNamedFamilies;
+ private final Map mAdditionalNamedFamilies;
+
private final List mAdditionalAliases;
public Result() {
@@ -54,13 +57,13 @@ public class FontCustomizationParser {
mAdditionalAliases = Collections.emptyList();
}
- public Result(Map additionalNamedFamilies,
+ public Result(Map additionalNamedFamilies,
List additionalAliases) {
mAdditionalNamedFamilies = additionalNamedFamilies;
mAdditionalAliases = additionalAliases;
}
- public Map getAdditionalNamedFamilies() {
+ public Map getAdditionalNamedFamilies() {
return mAdditionalNamedFamilies;
}
@@ -85,20 +88,24 @@ public class FontCustomizationParser {
return readFamilies(parser, fontDir, updatableFontMap);
}
- private static Map validateAndTransformToMap(List families) {
- HashMap namedFamily = new HashMap<>();
+ private static Result validateAndTransformToResult(
+ List families, List aliases) {
+ HashMap namedFamily = new HashMap<>();
for (int i = 0; i < families.size(); ++i) {
- final FontFamily family = families.get(i);
+ final NamedFamilyList family = families.get(i);
final String name = family.getName();
- if (name == null) {
- throw new IllegalArgumentException("new-named-family requires name attribute");
- }
- if (namedFamily.put(name, family) != null) {
+ if (name != null) {
+ if (namedFamily.put(name, family) != null) {
+ throw new IllegalArgumentException(
+ "new-named-family requires unique name attribute");
+ }
+ } else {
throw new IllegalArgumentException(
- "new-named-family requires unique name attribute");
+ "new-named-family requires name attribute or new-default-fallback-family"
+ + "requires fallackTarget attribute");
}
}
- return namedFamily;
+ return new Result(namedFamily, aliases);
}
private static Result readFamilies(
@@ -106,7 +113,7 @@ public class FontCustomizationParser {
@NonNull String fontDir,
@Nullable Map updatableFontMap
) throws XmlPullParserException, IOException {
- List families = new ArrayList<>();
+ List families = new ArrayList<>();
List aliases = new ArrayList<>();
parser.require(XmlPullParser.START_TAG, null, "fonts-modification");
while (parser.next() != XmlPullParser.END_TAG) {
@@ -114,19 +121,42 @@ public class FontCustomizationParser {
String tag = parser.getName();
if (tag.equals("family")) {
readFamily(parser, fontDir, families, updatableFontMap);
+ } else if (tag.equals("family-list")) {
+ readFamilyList(parser, fontDir, families, updatableFontMap);
} else if (tag.equals("alias")) {
aliases.add(FontListParser.readAlias(parser));
} else {
FontListParser.skip(parser);
}
}
- return new Result(validateAndTransformToMap(families), aliases);
+ return validateAndTransformToResult(families, aliases);
}
private static void readFamily(
@NonNull XmlPullParser parser,
@NonNull String fontDir,
- @NonNull List out,
+ @NonNull List out,
+ @Nullable Map updatableFontMap)
+ 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")) {
+ NamedFamilyList fontFamily = FontListParser.readNamedFamily(
+ parser, fontDir, updatableFontMap, false);
+ if (fontFamily != null) {
+ out.add(fontFamily);
+ }
+ } else {
+ throw new IllegalArgumentException("Unknown customizationType=" + customizationType);
+ }
+ }
+
+ private static void readFamilyList(
+ @NonNull XmlPullParser parser,
+ @NonNull String fontDir,
+ @NonNull List out,
@Nullable Map updatableFontMap)
throws XmlPullParserException, IOException {
final String customizationType = parser.getAttributeValue(null, "customizationType");
@@ -134,7 +164,7 @@ public class FontCustomizationParser {
throw new IllegalArgumentException("customizationType must be specified");
}
if (customizationType.equals("new-named-family")) {
- FontFamily fontFamily = FontListParser.readFamily(
+ NamedFamilyList fontFamily = FontListParser.readNamedFamilyList(
parser, fontDir, updatableFontMap, false);
if (fontFamily != null) {
out.add(fontFamily);
diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java
index a771a6e35345..bf79b1bedd8e 100644
--- a/graphics/java/android/graphics/fonts/FontFamily.java
+++ b/graphics/java/android/graphics/fonts/FontFamily.java
@@ -114,17 +114,19 @@ public final class FontFamily {
* @return a font family
*/
public @NonNull FontFamily build() {
- return build("", FontConfig.FontFamily.VARIANT_DEFAULT, true /* isCustomFallback */);
+ return build("", FontConfig.FontFamily.VARIANT_DEFAULT, true /* isCustomFallback */,
+ false /* isDefaultFallback */);
}
/** @hide */
public @NonNull FontFamily build(@NonNull String langTags, int variant,
- boolean isCustomFallback) {
+ boolean isCustomFallback, boolean isDefaultFallback) {
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 long ptr = nBuild(builderPtr, langTags, variant, isCustomFallback,
+ isDefaultFallback);
final FontFamily family = new FontFamily(ptr);
sFamilyRegistory.registerNativeAllocation(family, ptr);
return family;
@@ -138,7 +140,7 @@ public final class FontFamily {
@CriticalNative
private static native void nAddFont(long builderPtr, long fontPtr);
private static native long nBuild(long builderPtr, String langTags, int variant,
- boolean isCustomFallback);
+ boolean isCustomFallback, boolean isDefaultFallback);
@CriticalNative
private static native long nGetReleaseNativeFamily();
}
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
index 6278c0e23f27..ec8b2d66da7f 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -22,6 +22,7 @@ import android.graphics.FontListParser;
import android.graphics.Typeface;
import android.text.FontConfig;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
@@ -92,9 +93,8 @@ public final class SystemFonts {
}
private static void pushFamilyToFallback(@NonNull FontConfig.FontFamily xmlFamily,
- @NonNull ArrayMap> fallbackMap,
+ @NonNull ArrayMap fallbackMap,
@NonNull Map cache) {
-
final String languageTags = xmlFamily.getLocaleList().toLanguageTags();
final int variant = xmlFamily.getVariant();
@@ -118,26 +118,29 @@ public final class SystemFonts {
}
final FontFamily defaultFamily = defaultFonts.isEmpty() ? null : createFontFamily(
- xmlFamily.getName(), defaultFonts, languageTags, variant, cache);
+ defaultFonts, languageTags, variant, false, cache);
// Insert family into fallback map.
for (int i = 0; i < fallbackMap.size(); i++) {
- String name = fallbackMap.keyAt(i);
+ final String name = fallbackMap.keyAt(i);
+ final NativeFamilyListSet familyListSet = fallbackMap.valueAt(i);
+ if (familyListSet.seenXmlFamilies.contains(xmlFamily)) {
+ continue;
+ } else {
+ familyListSet.seenXmlFamilies.add(xmlFamily);
+ }
final ArrayList fallback = specificFallbackFonts.get(name);
if (fallback == null) {
- String familyName = xmlFamily.getName();
- if (defaultFamily != null
- // do not add myself to the fallback chain.
- && (familyName == null || !familyName.equals(name))) {
- fallbackMap.valueAt(i).add(defaultFamily);
+ if (defaultFamily != null) {
+ familyListSet.familyList.add(defaultFamily);
}
} else {
- final FontFamily family = createFontFamily(
- xmlFamily.getName(), fallback, languageTags, variant, cache);
+ final FontFamily family = createFontFamily(fallback, languageTags, variant, false,
+ cache);
if (family != null) {
- fallbackMap.valueAt(i).add(family);
+ familyListSet.familyList.add(family);
} else if (defaultFamily != null) {
- fallbackMap.valueAt(i).add(defaultFamily);
+ familyListSet.familyList.add(defaultFamily);
} else {
// There is no valid for for default fallback. Ignore.
}
@@ -145,10 +148,11 @@ public final class SystemFonts {
}
}
- private static @Nullable FontFamily createFontFamily(@NonNull String familyName,
+ private static @Nullable FontFamily createFontFamily(
@NonNull List fonts,
@NonNull String languageTags,
@FontConfig.FontFamily.Variant int variant,
+ boolean isDefaultFallback,
@NonNull Map cache) {
if (fonts.size() == 0) {
return null;
@@ -188,23 +192,30 @@ public final class SystemFonts {
b.addFont(font);
}
}
- return b == null ? null : b.build(languageTags, variant, false /* isCustomFallback */);
+ return b == null ? null : b.build(languageTags, variant, false /* isCustomFallback */,
+ isDefaultFallback);
}
- private static void appendNamedFamily(@NonNull FontConfig.FontFamily xmlFamily,
+ private static void appendNamedFamilyList(@NonNull FontConfig.NamedFamilyList namedFamilyList,
@NonNull ArrayMap bufferCache,
- @NonNull ArrayMap> fallbackListMap) {
- final String familyName = xmlFamily.getName();
- final FontFamily family = createFontFamily(
- familyName, xmlFamily.getFontList(),
- xmlFamily.getLocaleList().toLanguageTags(), xmlFamily.getVariant(),
- bufferCache);
- if (family == null) {
- return;
+ @NonNull ArrayMap fallbackListMap) {
+ final String familyName = namedFamilyList.getName();
+ final NativeFamilyListSet familyListSet = new NativeFamilyListSet();
+ final List xmlFamilies = namedFamilyList.getFamilies();
+ for (int i = 0; i < xmlFamilies.size(); ++i) {
+ FontConfig.FontFamily xmlFamily = xmlFamilies.get(i);
+ final FontFamily family = createFontFamily(
+ xmlFamily.getFontList(),
+ xmlFamily.getLocaleList().toLanguageTags(), xmlFamily.getVariant(),
+ true, // named family is always default
+ bufferCache);
+ if (family == null) {
+ return;
+ }
+ familyListSet.familyList.add(family);
+ familyListSet.seenXmlFamilies.add(xmlFamily);
}
- final ArrayList fallback = new ArrayList<>();
- fallback.add(family);
- fallbackListMap.put(familyName, fallback);
+ fallbackListMap.put(familyName, familyListSet);
}
/**
@@ -245,10 +256,12 @@ public final class SystemFonts {
updatableFontMap, lastModifiedDate, configVersion);
} catch (IOException e) {
Log.e(TAG, "Failed to open/read system font configurations.", e);
- return new FontConfig(Collections.emptyList(), Collections.emptyList(), 0, 0);
+ return new FontConfig(Collections.emptyList(), Collections.emptyList(),
+ Collections.emptyList(), 0, 0);
} catch (XmlPullParserException e) {
Log.e(TAG, "Failed to parse the system font configuration.", e);
- return new FontConfig(Collections.emptyList(), Collections.emptyList(), 0, 0);
+ return new FontConfig(Collections.emptyList(), Collections.emptyList(),
+ Collections.emptyList(), 0, 0);
}
}
@@ -261,37 +274,36 @@ public final class SystemFonts {
return buildSystemFallback(fontConfig, new ArrayMap<>());
}
+ private static final class NativeFamilyListSet {
+ public List familyList = new ArrayList<>();
+ public Set seenXmlFamilies = new ArraySet<>();
+ }
+
/** @hide */
@VisibleForTesting
public static Map buildSystemFallback(FontConfig fontConfig,
ArrayMap outBufferCache) {
- final Map fallbackMap = new ArrayMap<>();
- final List xmlFamilies = fontConfig.getFontFamilies();
- final ArrayMap> fallbackListMap = new ArrayMap<>();
- // First traverse families which have a 'name' attribute to create fallback map.
- for (final FontConfig.FontFamily xmlFamily : xmlFamilies) {
- final String familyName = xmlFamily.getName();
- if (familyName == null) {
- continue;
- }
- appendNamedFamily(xmlFamily, outBufferCache, fallbackListMap);
+ final ArrayMap fallbackListMap = new ArrayMap<>();
+
+ final List namedFamilies = fontConfig.getNamedFamilyLists();
+ for (int i = 0; i < namedFamilies.size(); ++i) {
+ FontConfig.NamedFamilyList namedFamilyList = namedFamilies.get(i);
+ appendNamedFamilyList(namedFamilyList, outBufferCache, fallbackListMap);
}
- // Then, add fallback fonts to the each fallback map.
+ // Then, add fallback fonts to the fallback map.
+ final List xmlFamilies = fontConfig.getFontFamilies();
for (int i = 0; i < xmlFamilies.size(); i++) {
final FontConfig.FontFamily xmlFamily = xmlFamilies.get(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, outBufferCache);
- }
+ pushFamilyToFallback(xmlFamily, fallbackListMap, outBufferCache);
}
// Build the font map and fallback map.
+ final Map fallbackMap = new ArrayMap<>();
for (int i = 0; i < fallbackListMap.size(); i++) {
final String fallbackName = fallbackListMap.keyAt(i);
- final List familyList = fallbackListMap.valueAt(i);
+ final List familyList = fallbackListMap.valueAt(i).familyList;
fallbackMap.put(fallbackName, familyList.toArray(new FontFamily[0]));
}
diff --git a/libs/hwui/jni/FontFamily.cpp b/libs/hwui/jni/FontFamily.cpp
index c146adac6b69..28e71d74e5b9 100644
--- a/libs/hwui/jni/FontFamily.cpp
+++ b/libs/hwui/jni/FontFamily.cpp
@@ -85,9 +85,9 @@ static jlong FontFamily_create(CRITICAL_JNI_PARAMS_COMMA jlong builderPtr) {
if (builder->fonts.empty()) {
return 0;
}
- std::shared_ptr family =
- minikin::FontFamily::create(builder->langId, builder->variant,
- std::move(builder->fonts), true /* isCustomFallback */);
+ std::shared_ptr family = minikin::FontFamily::create(
+ builder->langId, builder->variant, std::move(builder->fonts),
+ true /* isCustomFallback */, false /* isDefaultFallback */);
if (family->getCoverage().length() == 0) {
return 0;
}
diff --git a/libs/hwui/jni/fonts/FontFamily.cpp b/libs/hwui/jni/fonts/FontFamily.cpp
index fbfc07e1f89d..897c4d71c0d5 100644
--- a/libs/hwui/jni/fonts/FontFamily.cpp
+++ b/libs/hwui/jni/fonts/FontFamily.cpp
@@ -57,7 +57,8 @@ static void FontFamily_Builder_addFont(CRITICAL_JNI_PARAMS_COMMA jlong builderPt
// Regular JNI
static jlong FontFamily_Builder_build(JNIEnv* env, jobject clazz, jlong builderPtr,
- jstring langTags, jint variant, jboolean isCustomFallback) {
+ jstring langTags, jint variant, jboolean isCustomFallback,
+ jboolean isDefaultFallback) {
std::unique_ptr builder(toBuilder(builderPtr));
uint32_t localeId;
if (langTags == nullptr) {
@@ -66,9 +67,9 @@ static jlong FontFamily_Builder_build(JNIEnv* env, jobject clazz, jlong builderP
ScopedUtfChars str(env, langTags);
localeId = minikin::registerLocaleList(str.c_str());
}
- std::shared_ptr family =
- minikin::FontFamily::create(localeId, static_cast(variant),
- std::move(builder->fonts), isCustomFallback);
+ std::shared_ptr family = minikin::FontFamily::create(
+ localeId, static_cast(variant), std::move(builder->fonts),
+ isCustomFallback, isDefaultFallback);
if (family->getCoverage().length() == 0) {
// No coverage means minikin rejected given font for some reasons.
jniThrowException(env, "java/lang/IllegalArgumentException",
@@ -116,10 +117,10 @@ static jlong FontFamily_getFont(CRITICAL_JNI_PARAMS_COMMA jlong familyPtr, jint
///////////////////////////////////////////////////////////////////////////////
static const JNINativeMethod gFontFamilyBuilderMethods[] = {
- { "nInitBuilder", "()J", (void*) FontFamily_Builder_initBuilder },
- { "nAddFont", "(JJ)V", (void*) FontFamily_Builder_addFont },
- { "nBuild", "(JLjava/lang/String;IZ)J", (void*) FontFamily_Builder_build },
- { "nGetReleaseNativeFamily", "()J", (void*) FontFamily_Builder_GetReleaseFunc },
+ {"nInitBuilder", "()J", (void*)FontFamily_Builder_initBuilder},
+ {"nAddFont", "(JJ)V", (void*)FontFamily_Builder_addFont},
+ {"nBuild", "(JLjava/lang/String;IZZ)J", (void*)FontFamily_Builder_build},
+ {"nGetReleaseNativeFamily", "()J", (void*)FontFamily_Builder_GetReleaseFunc},
};
static const JNINativeMethod gFontFamilyMethods[] = {
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
index 4cd0d6e42121..2ac283370886 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
@@ -163,19 +163,25 @@ public class FontManagerShellCommand extends ShellCommand {
// Dump named font family first.
List families = fontConfig.getFontFamilies();
- w.println("Named Font Families");
+ // Dump FontFamilyList
+ w.println("Named Family List");
w.increaseIndent();
- for (int i = 0; i < families.size(); ++i) {
- final FontConfig.FontFamily family = families.get(i);
-
- // Here, only dump the named family only.
- if (family.getName() == null) continue;
-
- w.println("Named Family (" + family.getName() + ")");
- final List fonts = family.getFontList();
+ List namedFamilyLists = fontConfig.getNamedFamilyLists();
+ for (int i = 0; i < namedFamilyLists.size(); ++i) {
+ final FontConfig.NamedFamilyList namedFamilyList = namedFamilyLists.get(i);
+ w.println("Named Family (" + namedFamilyList.getName() + ")");
w.increaseIndent();
- for (int j = 0; j < fonts.size(); ++j) {
- dumpSingleFontConfig(w, fonts.get(j));
+ final List namedFamilies = namedFamilyList.getFamilies();
+ for (int j = 0; j < namedFamilies.size(); ++j) {
+ final FontConfig.FontFamily family = namedFamilies.get(j);
+
+ w.println("Family");
+ final List fonts = family.getFontList();
+ w.increaseIndent();
+ for (int k = 0; k < fonts.size(); ++k) {
+ dumpSingleFontConfig(w, fonts.get(k));
+ }
+ w.decreaseIndent();
}
w.decreaseIndent();
}
diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
index 6f9360844542..a680f5001479 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -19,7 +19,6 @@ package com.android.server.graphics.fonts;
import static com.android.server.graphics.fonts.FontManagerService.SystemFontException;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.graphics.fonts.FontManager;
import android.graphics.fonts.FontUpdateRequest;
import android.graphics.fonts.SystemFonts;
@@ -44,6 +43,7 @@ import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.SecureRandom;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -286,7 +286,7 @@ final class UpdatableFontDir {
// Before processing font family update, check all family points the available fonts.
for (FontUpdateRequest.Family family : familyMap.values()) {
- if (resolveFontFiles(family) == null) {
+ if (resolveFontFilesForNamedFamily(family) == null) {
throw new SystemFontException(
FontManager.RESULT_ERROR_FONT_NOT_FOUND,
"Required fonts are not available");
@@ -498,6 +498,19 @@ final class UpdatableFontDir {
}
}
}
+ for (int i = 0; i < fontConfig.getNamedFamilyLists().size(); ++i) {
+ FontConfig.NamedFamilyList namedFamilyList = fontConfig.getNamedFamilyLists().get(i);
+ for (int j = 0; j < namedFamilyList.getFamilies().size(); ++j) {
+ FontConfig.FontFamily family = namedFamilyList.getFamilies().get(j);
+ for (int k = 0; k < family.getFontList().size(); ++k) {
+ FontConfig.Font font = family.getFontList().get(k);
+ if (font.getPostScriptName().equals(psName)) {
+ targetFont = font;
+ break;
+ }
+ }
+ }
+ }
if (targetFont == null) {
return -1;
}
@@ -553,8 +566,8 @@ final class UpdatableFontDir {
}
}
- @Nullable
- private FontConfig.FontFamily resolveFontFiles(FontUpdateRequest.Family fontFamily) {
+ private FontConfig.NamedFamilyList resolveFontFilesForNamedFamily(
+ FontUpdateRequest.Family fontFamily) {
List fontList = fontFamily.getFonts();
List resolvedFonts = new ArrayList<>(fontList.size());
for (int i = 0; i < fontList.size(); i++) {
@@ -567,8 +580,10 @@ final class UpdatableFontDir {
resolvedFonts.add(new FontConfig.Font(info.mFile, null, info.getPostScriptName(),
font.getFontStyle(), font.getIndex(), font.getFontVariationSettings(), null));
}
- return new FontConfig.FontFamily(resolvedFonts, fontFamily.getName(),
+ FontConfig.FontFamily family = new FontConfig.FontFamily(resolvedFonts,
LocaleList.getEmptyLocaleList(), FontConfig.FontFamily.VARIANT_DEFAULT);
+ return new FontConfig.NamedFamilyList(Collections.singletonList(family),
+ fontFamily.getName());
}
Map getPostScriptMap() {
@@ -585,23 +600,24 @@ final class UpdatableFontDir {
PersistentSystemFontConfig.Config persistentConfig = readPersistentConfig();
List families = persistentConfig.fontFamilies;
- List mergedFamilies =
- new ArrayList<>(config.getFontFamilies().size() + families.size());
+ List mergedFamilies =
+ new ArrayList<>(config.getNamedFamilyLists().size() + families.size());
// We should keep the first font family (config.getFontFamilies().get(0)) because it's used
// as a fallback font. See SystemFonts.java.
- mergedFamilies.addAll(config.getFontFamilies());
+ mergedFamilies.addAll(config.getNamedFamilyLists());
// When building Typeface, a latter font family definition will override the previous font
// family definition with the same name. An exception is config.getFontFamilies.get(0),
// which will be used as a fallback font without being overridden.
for (int i = 0; i < families.size(); ++i) {
- FontConfig.FontFamily family = resolveFontFiles(families.get(i));
+ FontConfig.NamedFamilyList family = resolveFontFilesForNamedFamily(families.get(i));
if (family != null) {
mergedFamilies.add(family);
}
}
return new FontConfig(
- mergedFamilies, config.getAliases(), mLastModifiedMillis, mConfigVersion);
+ config.getFontFamilies(), config.getAliases(), mergedFamilies, mLastModifiedMillis,
+ mConfigVersion);
}
private PersistentSystemFontConfig.Config readPersistentConfig() {
@@ -635,12 +651,12 @@ final class UpdatableFontDir {
return mConfigVersion;
}
- public Map getFontFamilyMap() {
+ public Map getFontFamilyMap() {
PersistentSystemFontConfig.Config curConfig = readPersistentConfig();
- Map familyMap = new HashMap<>();
+ Map familyMap = new HashMap<>();
for (int i = 0; i < curConfig.fontFamilies.size(); ++i) {
FontUpdateRequest.Family family = curConfig.fontFamilies.get(i);
- FontConfig.FontFamily resolvedFamily = resolveFontFiles(family);
+ FontConfig.NamedFamilyList resolvedFamily = resolveFontFilesForNamedFamily(family);
if (resolvedFamily != null) {
familyMap.put(family.getName(), resolvedFamily);
}
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
index 68e5ebf027ad..e9a7d85ae755 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
@@ -210,7 +210,8 @@ public final class UpdatableFontDirTest {
assertThat(mUpdatableFontFilesDir.list()).hasLength(2);
assertNamedFamilyExists(dir.getSystemFontConfig(), "foobar");
assertThat(dir.getFontFamilyMap()).containsKey("foobar");
- FontConfig.FontFamily foobar = dir.getFontFamilyMap().get("foobar");
+ assertThat(dir.getFontFamilyMap().get("foobar").getFamilies().size()).isEqualTo(1);
+ FontConfig.FontFamily foobar = dir.getFontFamilyMap().get("foobar").getFamilies().get(0);
assertThat(foobar.getFontList()).hasSize(2);
assertThat(foobar.getFontList().get(0).getFile())
.isEqualTo(dir.getPostScriptMap().get("foo"));
@@ -326,10 +327,12 @@ public final class UpdatableFontDirTest {
new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT), 0, null, null);
FontConfig.FontFamily family = new FontConfig.FontFamily(
- Arrays.asList(fooFont, barFont), "sans-serif", null,
+ Arrays.asList(fooFont, barFont), null,
FontConfig.FontFamily.VARIANT_DEFAULT);
- return new FontConfig(Collections.singletonList(family),
- Collections.emptyList(), 0, 1);
+ return new FontConfig(Collections.emptyList(),
+ Collections.emptyList(),
+ Collections.singletonList(new FontConfig.NamedFamilyList(
+ Collections.singletonList(family), "sans-serif")), 0, 1);
};
UpdatableFontDir dirForPreparation = new UpdatableFontDir(
@@ -411,7 +414,8 @@ public final class UpdatableFontDirTest {
assertThat(dir.getPostScriptMap()).containsKey("foo");
assertThat(mParser.getRevision(dir.getPostScriptMap().get("foo"))).isEqualTo(1);
assertThat(dir.getFontFamilyMap()).containsKey("foobar");
- FontConfig.FontFamily foobar = dir.getFontFamilyMap().get("foobar");
+ assertThat(dir.getFontFamilyMap().get("foobar").getFamilies().size()).isEqualTo(1);
+ FontConfig.FontFamily foobar = dir.getFontFamilyMap().get("foobar").getFamilies().get(0);
assertThat(foobar.getFontList()).hasSize(1);
assertThat(foobar.getFontList().get(0).getFile())
.isEqualTo(dir.getPostScriptMap().get("foo"));
@@ -485,10 +489,12 @@ public final class UpdatableFontDirTest {
file, null, "bar", new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT),
0, null, null);
FontConfig.FontFamily family = new FontConfig.FontFamily(
- Collections.singletonList(font), "sans-serif", null,
- FontConfig.FontFamily.VARIANT_DEFAULT);
- return new FontConfig(Collections.singletonList(family),
- Collections.emptyList(), 0, 1);
+ Collections.singletonList(font), null, FontConfig.FontFamily.VARIANT_DEFAULT);
+ return new FontConfig(
+ Collections.emptyList(),
+ Collections.emptyList(),
+ Collections.singletonList(new FontConfig.NamedFamilyList(
+ Collections.singletonList(family), "sans-serif")), 0, 1);
});
dir.loadFontFileMap();
@@ -636,9 +642,10 @@ public final class UpdatableFontDirTest {
file, null, "test", new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT), 0, null,
null);
FontConfig.FontFamily family = new FontConfig.FontFamily(
- Collections.singletonList(font), "sans-serif", null,
- FontConfig.FontFamily.VARIANT_DEFAULT);
- return new FontConfig(Collections.singletonList(family), Collections.emptyList(), 0, 1);
+ Collections.singletonList(font), null, FontConfig.FontFamily.VARIANT_DEFAULT);
+ return new FontConfig(Collections.emptyList(), Collections.emptyList(),
+ Collections.singletonList(new FontConfig.NamedFamilyList(
+ Collections.singletonList(family), "sans-serif")), 0, 1);
});
dir.loadFontFileMap();
@@ -873,13 +880,14 @@ public final class UpdatableFontDirTest {
+ "")));
assertThat(dir.getPostScriptMap()).containsKey("test");
assertThat(dir.getFontFamilyMap()).containsKey("test");
- FontConfig.FontFamily test = dir.getFontFamilyMap().get("test");
+ assertThat(dir.getFontFamilyMap().get("test").getFamilies().size()).isEqualTo(1);
+ FontConfig.FontFamily test = dir.getFontFamilyMap().get("test").getFamilies().get(0);
assertThat(test.getFontList()).hasSize(1);
assertThat(test.getFontList().get(0).getFile())
.isEqualTo(dir.getPostScriptMap().get("test"));
}
- @Test
+ @Test(expected = IllegalArgumentException.class)
public void addFontFamily_noName() throws Exception {
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
@@ -891,12 +899,7 @@ public final class UpdatableFontDirTest {
newAddFontFamilyRequest(""
+ " test.ttf"
+ ""));
- try {
- dir.update(requests);
- fail("Expect NullPointerException");
- } catch (NullPointerException e) {
- // Expect
- }
+ dir.update(requests);
}
@Test
@@ -955,17 +958,16 @@ public final class UpdatableFontDirTest {
dir.loadFontFileMap();
assertThat(dir.getSystemFontConfig().getFontFamilies()).isNotEmpty();
FontConfig.FontFamily firstFontFamily = dir.getSystemFontConfig().getFontFamilies().get(0);
- assertThat(firstFontFamily.getName()).isNotEmpty();
dir.update(Arrays.asList(
newFontUpdateRequest("test.ttf,1,test", GOOD_SIGNATURE),
- newAddFontFamilyRequest(""
+ newAddFontFamilyRequest(""
+ " test.ttf"
+ "")));
FontConfig fontConfig = dir.getSystemFontConfig();
assertThat(dir.getSystemFontConfig().getFontFamilies()).isNotEmpty();
assertThat(fontConfig.getFontFamilies().get(0)).isEqualTo(firstFontFamily);
- FontConfig.FontFamily updated = getLastFamily(fontConfig, firstFontFamily.getName());
+ FontConfig.FontFamily updated = getLastFamily(fontConfig, "sans-serif");
assertThat(updated.getFontList()).hasSize(1);
assertThat(updated.getFontList().get(0).getFile())
.isEqualTo(dir.getPostScriptMap().get("test"));
@@ -1005,7 +1007,9 @@ public final class UpdatableFontDirTest {
mParser.setInput(is, "UTF-8");
mParser.nextTag();
- FontConfig.FontFamily fontFamily = FontListParser.readFamily(mParser, "", null, true);
+ FontConfig.NamedFamilyList namedFamilyList = FontListParser.readNamedFamily(
+ mParser, "", null, true);
+ FontConfig.FontFamily fontFamily = namedFamilyList.getFamilies().get(0);
List fonts = new ArrayList<>();
for (FontConfig.Font font : fontFamily.getFontList()) {
String name = font.getFile().getName();
@@ -1014,7 +1018,8 @@ public final class UpdatableFontDirTest {
psName, font.getStyle(), font.getTtcIndex(), font.getFontVariationSettings());
fonts.add(updateFont);
}
- FontUpdateRequest.Family family = new FontUpdateRequest.Family(fontFamily.getName(), fonts);
+ FontUpdateRequest.Family family = new FontUpdateRequest.Family(
+ namedFamilyList.getName(), fonts);
return new FontUpdateRequest(family);
}
@@ -1035,18 +1040,18 @@ public final class UpdatableFontDirTest {
// Returns the last family with the given name, which will be used for creating Typeface.
private static FontConfig.FontFamily getLastFamily(FontConfig fontConfig, String familyName) {
- List fontFamilies = fontConfig.getFontFamilies();
- for (int i = fontFamilies.size() - 1; i >= 0; i--) {
- if (familyName.equals(fontFamilies.get(i).getName())) {
- return fontFamilies.get(i);
+ List namedFamilyLists = fontConfig.getNamedFamilyLists();
+ for (int i = namedFamilyLists.size() - 1; i >= 0; i--) {
+ if (familyName.equals(namedFamilyLists.get(i).getName())) {
+ return namedFamilyLists.get(i).getFamilies().get(0);
}
}
return null;
}
private static void assertNamedFamilyExists(FontConfig fontConfig, String familyName) {
- assertThat(fontConfig.getFontFamilies().stream()
- .map(FontConfig.FontFamily::getName)
+ assertThat(fontConfig.getNamedFamilyLists().stream()
+ .map(FontConfig.NamedFamilyList::getName)
.collect(Collectors.toSet())).contains(familyName);
}
}
diff --git a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
index 650686ff3b85..fa5b7c15a6fe 100644
--- a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
+++ b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
@@ -69,6 +69,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
+import java.util.stream.Stream;
/**
* Tests if fonts can be updated by {@link FontManager} API.
@@ -246,7 +247,10 @@ public class UpdatableSystemFontTest {
@Test
public void updateFontFamily() throws Exception {
assertThat(updateNotoSerifAs("serif")).isEqualTo(FontManager.RESULT_SUCCESS);
- FontConfig.FontFamily family = findFontFamilyOrThrow("serif");
+ final FontConfig.NamedFamilyList namedFamilyList = findFontFamilyOrThrow("serif");
+ assertThat(namedFamilyList.getFamilies().size()).isEqualTo(1);
+ final FontConfig.FontFamily family = namedFamilyList.getFamilies().get(0);
+
assertThat(family.getFontList()).hasSize(2);
assertThat(family.getFontList().get(0).getPostScriptName())
.isEqualTo(NOTO_SERIF_REGULAR_POSTSCRIPT_NAME);
@@ -265,7 +269,10 @@ public class UpdatableSystemFontTest {
public void updateFontFamily_asNewFont() throws Exception {
assertThat(updateNotoSerifAs("UpdatableSystemFontTest-serif"))
.isEqualTo(FontManager.RESULT_SUCCESS);
- FontConfig.FontFamily family = findFontFamilyOrThrow("UpdatableSystemFontTest-serif");
+ final FontConfig.NamedFamilyList namedFamilyList =
+ findFontFamilyOrThrow("UpdatableSystemFontTest-serif");
+ assertThat(namedFamilyList.getFamilies().size()).isEqualTo(1);
+ final FontConfig.FontFamily family = namedFamilyList.getFamilies().get(0);
assertThat(family.getFontList()).hasSize(2);
assertThat(family.getFontList().get(0).getPostScriptName())
.isEqualTo(NOTO_SERIF_REGULAR_POSTSCRIPT_NAME);
@@ -434,9 +441,15 @@ public class UpdatableSystemFontTest {
private String getFontPath(String psName) {
FontConfig fontConfig =
SystemUtil.runWithShellPermissionIdentity(mFontManager::getFontConfig);
- return fontConfig.getFontFamilies().stream()
+ final List namedFamilies = fontConfig.getNamedFamilyLists().stream()
+ .flatMap(namedFamily -> namedFamily.getFamilies().stream()).toList();
+
+ return Stream.concat(fontConfig.getFontFamilies().stream(), namedFamilies.stream())
.flatMap(family -> family.getFontList().stream())
- .filter(font -> psName.equals(font.getPostScriptName()))
+ .filter(font -> {
+ Log.e("Debug", "PsName = " + font.getPostScriptName());
+ return psName.equals(font.getPostScriptName());
+ })
// Return the last match, because the latter family takes precedence if two families
// have the same name.
.reduce((first, second) -> second)
@@ -445,10 +458,10 @@ public class UpdatableSystemFontTest {
.getAbsolutePath();
}
- private FontConfig.FontFamily findFontFamilyOrThrow(String familyName) {
+ private FontConfig.NamedFamilyList findFontFamilyOrThrow(String familyName) {
FontConfig fontConfig =
SystemUtil.runWithShellPermissionIdentity(mFontManager::getFontConfig);
- return fontConfig.getFontFamilies().stream()
+ return fontConfig.getNamedFamilyLists().stream()
.filter(family -> familyName.equals(family.getName()))
// Return the last match, because the latter family takes precedence if two families
// have the same name.
--
cgit v1.2.3-59-g8ed1b
From 741dcc52bd3e2953bc3ca4c2db1d266ade3663fa Mon Sep 17 00:00:00 2001
From: Emilian Peev
Date: Mon, 10 Oct 2022 16:18:13 -0700
Subject: Camera: Add support for Jpeg/R
Introduce support for the new Jpeg/R format that includes 10-bit recovery
map.
Bug: 241284696
Test: atest -c -d
cts/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java#testJpegR
Change-Id: I85f4cf130ab37b351595608943c92ce2844c40cb
---
core/api/current.txt | 2 +
core/java/android/hardware/DataSpace.java | 14 +++
.../hardware/camera2/CameraCharacteristics.java | 109 +++++++++++++++++++++
.../android/hardware/camera2/CameraDevice.java | 22 +++++
.../camera2/impl/CameraMetadataNative.java | 26 +++++
.../camera2/params/StreamConfigurationMap.java | 88 ++++++++++++++++-
graphics/java/android/graphics/ImageFormat.java | 12 ++-
media/java/android/media/ImageReader.java | 2 +
media/java/android/media/ImageUtils.java | 3 +
9 files changed, 274 insertions(+), 4 deletions(-)
(limited to 'graphics/java')
diff --git a/core/api/current.txt b/core/api/current.txt
index 877e2d6b54b4..0f9db81baab9 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -15032,6 +15032,7 @@ package android.graphics {
field public static final int FLEX_RGB_888 = 41; // 0x29
field public static final int HEIC = 1212500294; // 0x48454946
field public static final int JPEG = 256; // 0x100
+ field public static final int JPEG_R = 4101; // 0x1005
field public static final int NV16 = 16; // 0x10
field public static final int NV21 = 17; // 0x11
field public static final int PRIVATE = 34; // 0x22
@@ -17213,6 +17214,7 @@ package android.hardware {
field public static final int DATASPACE_DYNAMIC_DEPTH = 4098; // 0x1002
field public static final int DATASPACE_HEIF = 4100; // 0x1004
field public static final int DATASPACE_JFIF = 146931712; // 0x8c20000
+ field public static final int DATASPACE_JPEG_R = 4101; // 0x1005
field public static final int DATASPACE_SCRGB = 411107328; // 0x18810000
field public static final int DATASPACE_SCRGB_LINEAR = 406913024; // 0x18410000
field public static final int DATASPACE_SRGB = 142671872; // 0x8810000
diff --git a/core/java/android/hardware/DataSpace.java b/core/java/android/hardware/DataSpace.java
index 15eae0920e7d..0a145746d303 100644
--- a/core/java/android/hardware/DataSpace.java
+++ b/core/java/android/hardware/DataSpace.java
@@ -408,6 +408,19 @@ public final class DataSpace {
*/
public static final int DATASPACE_HEIF = 4100;
+ /**
+ * ISO/IEC TBD
+ *
+ * JPEG image with embedded recovery map following the Jpeg/R specification.
+ *
+ * This value must always remain aligned with the public ImageFormat Jpeg/R definition and is
+ * valid with formats:
+ * HAL_PIXEL_FORMAT_BLOB: JPEG image encoded by Jpeg/R encoder according to ISO/IEC TBD.
+ * The image contains a standard SDR JPEG and a recovery map. Jpeg/R decoders can use the
+ * map to recover the input image.
+ */
+ public static final int DATASPACE_JPEG_R = 4101;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {
@@ -626,6 +639,7 @@ public final class DataSpace {
DATASPACE_DEPTH,
DATASPACE_DYNAMIC_DEPTH,
DATASPACE_HEIF,
+ DATASPACE_JPEG_R,
DATASPACE_UNKNOWN,
DATASPACE_SCRGB_LINEAR,
DATASPACE_SRGB,
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 6e72b5f291f0..a6f7e945bef8 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -5562,6 +5562,115 @@ public final class CameraCharacteristics extends CameraMetadata AUTOMOTIVE_LOCATION =
new Key("android.automotive.location", int.class);
+ /**
+ * The available Jpeg/R stream
+ * configurations that this camera device supports
+ * (i.e. format, width, height, output/input stream).
+ * The configurations are listed as (format, width, height, input?) tuples.
+ * If the camera device supports Jpeg/R, it will support the same stream combinations with
+ * Jpeg/R as it does with P010. The stream combinations with Jpeg/R (or P010) supported
+ * by the device is determined by the device's hardware level and capabilities.
+ * All the static, control, and dynamic metadata tags related to JPEG apply to Jpeg/R formats.
+ * Configuring JPEG and Jpeg/R streams at the same time is not supported.
+ * Optional - The value for this key may be {@code null} on some devices.
+ * Limited capability -
+ * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+ * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key
+ *
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @hide
+ */
+ public static final Key JPEGR_AVAILABLE_JPEG_R_STREAM_CONFIGURATIONS =
+ new Key("android.jpegr.availableJpegRStreamConfigurations", android.hardware.camera2.params.StreamConfiguration[].class);
+
+ /**
+ * This lists the minimum frame duration for each
+ * format/size combination for Jpeg/R output formats.
+ * This should correspond to the frame duration when only that
+ * stream is active, with all processing (typically in android.*.mode)
+ * set to either OFF or FAST.
+ * When multiple streams are used in a request, the minimum frame
+ * duration will be max(individual stream min durations).
+ * See {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} and
+ * android.scaler.availableStallDurations for more details about
+ * calculating the max frame rate.
+ * Units: (format, width, height, ns) x n
+ * Optional - The value for this key may be {@code null} on some devices.
+ * Limited capability -
+ * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+ * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key
+ *
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see CaptureRequest#SENSOR_FRAME_DURATION
+ * @hide
+ */
+ public static final Key JPEGR_AVAILABLE_JPEG_R_MIN_FRAME_DURATIONS =
+ new Key("android.jpegr.availableJpegRMinFrameDurations", android.hardware.camera2.params.StreamConfigurationDuration[].class);
+
+ /**
+ * This lists the maximum stall duration for each
+ * output format/size combination for Jpeg/R streams.
+ * A stall duration is how much extra time would get added
+ * to the normal minimum frame duration for a repeating request
+ * that has streams with non-zero stall.
+ * This functions similarly to
+ * android.scaler.availableStallDurations for Jpeg/R
+ * streams.
+ * All Jpeg/R output stream formats may have a nonzero stall
+ * duration.
+ * Units: (format, width, height, ns) x n
+ * Optional - The value for this key may be {@code null} on some devices.
+ * Limited capability -
+ * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+ * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key
+ *
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @hide
+ */
+ public static final Key JPEGR_AVAILABLE_JPEG_R_STALL_DURATIONS =
+ new Key("android.jpegr.availableJpegRStallDurations", android.hardware.camera2.params.StreamConfigurationDuration[].class);
+
+ /**
+ * The available Jpeg/R stream
+ * configurations that this camera device supports
+ * (i.e. format, width, height, output/input stream).
+ * Refer to android.jpegr.availableJpegRStreamConfigurations for details.
+ * Optional - The value for this key may be {@code null} on some devices.
+ * @hide
+ */
+ public static final Key JPEGR_AVAILABLE_JPEG_R_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION =
+ new Key("android.jpegr.availableJpegRStreamConfigurationsMaximumResolution", android.hardware.camera2.params.StreamConfiguration[].class);
+
+ /**
+ * This lists the minimum frame duration for each
+ * format/size combination for Jpeg/R output formats for CaptureRequests where
+ * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.
+ * Refer to android.jpegr.availableJpegRMinFrameDurations for details.
+ * Units: (format, width, height, ns) x n
+ * Optional - The value for this key may be {@code null} on some devices.
+ *
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
+ * @hide
+ */
+ public static final Key JPEGR_AVAILABLE_JPEG_R_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION =
+ new Key("android.jpegr.availableJpegRMinFrameDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class);
+
+ /**
+ * This lists the maximum stall duration for each
+ * output format/size combination for Jpeg/R streams for CaptureRequests where
+ * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.
+ * Refer to android.jpegr.availableJpegRStallDurations for details.
+ * Units: (format, width, height, ns) x n
+ * Optional - The value for this key may be {@code null} on some devices.
+ *
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
+ * @hide
+ */
+ public static final Key JPEGR_AVAILABLE_JPEG_R_STALL_DURATIONS_MAXIMUM_RESOLUTION =
+ new Key("android.jpegr.availableJpegRStallDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class);
+
/*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
* End generated code
*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 10a7538cf488..bf2e56359785 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -859,6 +859,28 @@ public abstract class CameraDevice implements AutoCloseable {
* format {@link android.graphics.ImageFormat#YUV_420_888} with a 10-bit profile
* will cause a capture session initialization failure.
*
+ * {@link android.graphics.ImageFormat#JPEG_R} may also be supported if advertised by
+ * {@link android.hardware.camera2.params.StreamConfigurationMap}. When initializing a capture
+ * session that includes a Jpeg/R camera output clients must consider the following items w.r.t.
+ * the 10-bit mandatory stream combination table:
+ *
+ *
+ * - To generate the compressed Jpeg/R image a single
+ * {@link android.graphics.ImageFormat#YCBCR_P010} output will be used internally by
+ * the camera device.
+ * - On camera devices that are able to support concurrent 10 and 8-bit capture requests
+ * see {@link android.hardware.camera2.params.DynamicRangeProfiles#getProfileCaptureRequestConstraints}
+ * an extra {@link android.graphics.ImageFormat#JPEG} will also
+ * be configured internally to help speed up the encoding process.
+ *
+ *
+ * Jpeg/R camera outputs will typically be able to support the MAXIMUM device resolution.
+ * Clients can also call {@link StreamConfigurationMap#getOutputSizes(int)} for a complete list
+ * supported sizes.
+ * Camera clients that register a Jpeg/R output within a stream combination that doesn't fit
+ * in the mandatory stream table above can call
+ * {@link CameraDevice#isSessionConfigurationSupported} to ensure that this particular
+ * configuration is supported.
*
* Devices with the STREAM_USE_CASE capability ({@link
* CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES} includes {@link
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 012fad52fddf..9a164743bc8d 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -1351,6 +1351,9 @@ public class CameraMetadataNative implements Parcelable {
/*heicconfiguration*/ null,
/*heicminduration*/ null,
/*heicstallduration*/ null,
+ /*jpegRconfiguration*/ null,
+ /*jpegRminduration*/ null,
+ /*jpegRstallduration*/ null,
/*highspeedvideoconfigurations*/ null,
/*inputoutputformatsmap*/ null, listHighResolution, supportsPrivate[i]);
break;
@@ -1365,6 +1368,9 @@ public class CameraMetadataNative implements Parcelable {
/*heicconfiguration*/ null,
/*heicminduration*/ null,
/*heicstallduration*/ null,
+ /*jpegRconfiguration*/ null,
+ /*jpegRminduration*/ null,
+ /*jpegRstallduration*/ null,
highSpeedVideoConfigurations,
/*inputoutputformatsmap*/ null, listHighResolution, supportsPrivate[i]);
break;
@@ -1379,6 +1385,9 @@ public class CameraMetadataNative implements Parcelable {
/*heicconfiguration*/ null,
/*heicminduration*/ null,
/*heicstallduration*/ null,
+ /*jpegRconfiguration*/ null,
+ /*jpegRminduration*/ null,
+ /*jpegRstallduration*/ null,
/*highSpeedVideoConfigurations*/ null,
inputOutputFormatsMap, listHighResolution, supportsPrivate[i]);
break;
@@ -1393,6 +1402,9 @@ public class CameraMetadataNative implements Parcelable {
/*heicconfiguration*/ null,
/*heicminduration*/ null,
/*heicstallduration*/ null,
+ /*jpegRconfiguration*/ null,
+ /*jpegRminduration*/ null,
+ /*jpegRstallduration*/ null,
/*highSpeedVideoConfigurations*/ null,
/*inputOutputFormatsMap*/ null, listHighResolution, supportsPrivate[i]);
}
@@ -1546,6 +1558,12 @@ public class CameraMetadataNative implements Parcelable {
CameraCharacteristics.HEIC_AVAILABLE_HEIC_MIN_FRAME_DURATIONS);
StreamConfigurationDuration[] heicStallDurations = getBase(
CameraCharacteristics.HEIC_AVAILABLE_HEIC_STALL_DURATIONS);
+ StreamConfiguration[] jpegRConfigurations = getBase(
+ CameraCharacteristics.JPEGR_AVAILABLE_JPEG_R_STREAM_CONFIGURATIONS);
+ StreamConfigurationDuration[] jpegRMinFrameDurations = getBase(
+ CameraCharacteristics.JPEGR_AVAILABLE_JPEG_R_MIN_FRAME_DURATIONS);
+ StreamConfigurationDuration[] jpegRStallDurations = getBase(
+ CameraCharacteristics.JPEGR_AVAILABLE_JPEG_R_STALL_DURATIONS);
HighSpeedVideoConfiguration[] highSpeedVideoConfigurations = getBase(
CameraCharacteristics.CONTROL_AVAILABLE_HIGH_SPEED_VIDEO_CONFIGURATIONS);
ReprocessFormatsMap inputOutputFormatsMap = getBase(
@@ -1557,6 +1575,7 @@ public class CameraMetadataNative implements Parcelable {
dynamicDepthConfigurations, dynamicDepthMinFrameDurations,
dynamicDepthStallDurations, heicConfigurations,
heicMinFrameDurations, heicStallDurations,
+ jpegRConfigurations, jpegRMinFrameDurations, jpegRStallDurations,
highSpeedVideoConfigurations, inputOutputFormatsMap,
listHighResolution);
}
@@ -1589,6 +1608,12 @@ public class CameraMetadataNative implements Parcelable {
CameraCharacteristics.HEIC_AVAILABLE_HEIC_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION);
StreamConfigurationDuration[] heicStallDurations = getBase(
CameraCharacteristics.HEIC_AVAILABLE_HEIC_STALL_DURATIONS_MAXIMUM_RESOLUTION);
+ StreamConfiguration[] jpegRConfigurations = getBase(
+ CameraCharacteristics.JPEGR_AVAILABLE_JPEG_R_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION);
+ StreamConfigurationDuration[] jpegRMinFrameDurations = getBase(
+ CameraCharacteristics.JPEGR_AVAILABLE_JPEG_R_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION);
+ StreamConfigurationDuration[] jpegRStallDurations = getBase(
+ CameraCharacteristics.JPEGR_AVAILABLE_JPEG_R_STALL_DURATIONS_MAXIMUM_RESOLUTION);
HighSpeedVideoConfiguration[] highSpeedVideoConfigurations = getBase(
CameraCharacteristics.CONTROL_AVAILABLE_HIGH_SPEED_VIDEO_CONFIGURATIONS_MAXIMUM_RESOLUTION);
ReprocessFormatsMap inputOutputFormatsMap = getBase(
@@ -1601,6 +1626,7 @@ public class CameraMetadataNative implements Parcelable {
dynamicDepthConfigurations, dynamicDepthMinFrameDurations,
dynamicDepthStallDurations, heicConfigurations,
heicMinFrameDurations, heicStallDurations,
+ jpegRConfigurations, jpegRMinFrameDurations, jpegRStallDurations,
highSpeedVideoConfigurations, inputOutputFormatsMap,
listHighResolution, false);
}
diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
index 5981d279227d..cb678b98a998 100644
--- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
@@ -26,6 +26,7 @@ import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.utils.HashCodeHelpers;
import android.hardware.camera2.utils.SurfaceUtils;
+import android.util.Log;
import android.util.Range;
import android.util.Size;
import android.util.SparseIntArray;
@@ -95,6 +96,11 @@ public final class StreamConfigurationMap {
* {@link StreamConfigurationDuration}
* @param heicStallDurations a non-{@code null} array of heic
* {@link StreamConfigurationDuration}
+ * @param jpegRConfigurations a non-{@code null} array of Jpeg/R {@link StreamConfiguration}
+ * @param jpegRMinFrameDurations a non-{@code null} array of Jpeg/R
+ * {@link StreamConfigurationDuration}
+ * @param jpegRStallDurations a non-{@code null} array of Jpeg/R
+ * {@link StreamConfigurationDuration}
* @param highSpeedVideoConfigurations an array of {@link HighSpeedVideoConfiguration}, null if
* camera device does not support high speed video recording
* @param listHighResolution a flag indicating whether the device supports BURST_CAPTURE
@@ -117,6 +123,9 @@ public final class StreamConfigurationMap {
StreamConfiguration[] heicConfigurations,
StreamConfigurationDuration[] heicMinFrameDurations,
StreamConfigurationDuration[] heicStallDurations,
+ StreamConfiguration[] jpegRConfigurations,
+ StreamConfigurationDuration[] jpegRMinFrameDurations,
+ StreamConfigurationDuration[] jpegRStallDurations,
HighSpeedVideoConfiguration[] highSpeedVideoConfigurations,
ReprocessFormatsMap inputOutputFormatsMap,
boolean listHighResolution) {
@@ -125,6 +134,7 @@ public final class StreamConfigurationMap {
dynamicDepthConfigurations, dynamicDepthMinFrameDurations,
dynamicDepthStallDurations,
heicConfigurations, heicMinFrameDurations, heicStallDurations,
+ jpegRConfigurations, jpegRMinFrameDurations, jpegRStallDurations,
highSpeedVideoConfigurations, inputOutputFormatsMap, listHighResolution,
/*enforceImplementationDefined*/ true);
}
@@ -154,6 +164,11 @@ public final class StreamConfigurationMap {
* {@link StreamConfigurationDuration}
* @param heicStallDurations a non-{@code null} array of heic
* {@link StreamConfigurationDuration}
+ * @param jpegRConfigurations a non-{@code null} array of Jpeg/R {@link StreamConfiguration}
+ * @param jpegRMinFrameDurations a non-{@code null} array of Jpeg/R
+ * {@link StreamConfigurationDuration}
+ * @param jpegRStallDurations a non-{@code null} array of Jpeg/R
+ * {@link StreamConfigurationDuration}
* @param highSpeedVideoConfigurations an array of {@link HighSpeedVideoConfiguration}, null if
* camera device does not support high speed video recording
* @param listHighResolution a flag indicating whether the device supports BURST_CAPTURE
@@ -178,6 +193,9 @@ public final class StreamConfigurationMap {
StreamConfiguration[] heicConfigurations,
StreamConfigurationDuration[] heicMinFrameDurations,
StreamConfigurationDuration[] heicStallDurations,
+ StreamConfiguration[] jpegRConfigurations,
+ StreamConfigurationDuration[] jpegRMinFrameDurations,
+ StreamConfigurationDuration[] jpegRStallDurations,
HighSpeedVideoConfiguration[] highSpeedVideoConfigurations,
ReprocessFormatsMap inputOutputFormatsMap,
boolean listHighResolution,
@@ -242,6 +260,20 @@ public final class StreamConfigurationMap {
"heicStallDurations");
}
+
+ if (jpegRConfigurations == null) {
+ mJpegRConfigurations = new StreamConfiguration[0];
+ mJpegRMinFrameDurations = new StreamConfigurationDuration[0];
+ mJpegRStallDurations = new StreamConfigurationDuration[0];
+ } else {
+ mJpegRConfigurations = checkArrayElementsNotNull(jpegRConfigurations,
+ "jpegRConfigurations");
+ mJpegRMinFrameDurations = checkArrayElementsNotNull(jpegRMinFrameDurations,
+ "jpegRFrameDurations");
+ mJpegRStallDurations = checkArrayElementsNotNull(jpegRStallDurations,
+ "jpegRStallDurations");
+ }
+
if (highSpeedVideoConfigurations == null) {
mHighSpeedVideoConfigurations = new HighSpeedVideoConfiguration[0];
} else {
@@ -305,6 +337,17 @@ public final class StreamConfigurationMap {
mHeicOutputFormats.get(config.getFormat()) + 1);
}
+ // For each Jpeg/R format, track how many sizes there are available to configure
+ for (StreamConfiguration config : mJpegRConfigurations) {
+ if (!config.isOutput()) {
+ // Ignoring input Jpeg/R configs
+ continue;
+ }
+
+ mJpegROutputFormats.put(config.getFormat(),
+ mJpegROutputFormats.get(config.getFormat()) + 1);
+ }
+
if (configurations != null && enforceImplementationDefined &&
mOutputFormats.indexOfKey(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED) < 0) {
throw new AssertionError(
@@ -447,6 +490,8 @@ public final class StreamConfigurationMap {
return mDynamicDepthOutputFormats.indexOfKey(internalFormat) >= 0;
} else if (dataspace == HAL_DATASPACE_HEIF) {
return mHeicOutputFormats.indexOfKey(internalFormat) >= 0;
+ } else if (dataspace == HAL_DATASPACE_JPEG_R) {
+ return mJpegROutputFormats.indexOfKey(internalFormat) >= 0;
} else {
return getFormatsMap(/*output*/true).indexOfKey(internalFormat) >= 0;
}
@@ -561,6 +606,7 @@ public final class StreamConfigurationMap {
surfaceDataspace == HAL_DATASPACE_DEPTH ? mDepthConfigurations :
surfaceDataspace == HAL_DATASPACE_DYNAMIC_DEPTH ? mDynamicDepthConfigurations :
surfaceDataspace == HAL_DATASPACE_HEIF ? mHeicConfigurations :
+ surfaceDataspace == HAL_DATASPACE_JPEG_R ? mJpegRConfigurations :
mConfigurations;
for (StreamConfiguration config : configs) {
if (config.getFormat() == surfaceFormat && config.isOutput()) {
@@ -597,6 +643,7 @@ public final class StreamConfigurationMap {
dataspace == HAL_DATASPACE_DEPTH ? mDepthConfigurations :
dataspace == HAL_DATASPACE_DYNAMIC_DEPTH ? mDynamicDepthConfigurations :
dataspace == HAL_DATASPACE_HEIF ? mHeicConfigurations :
+ dataspace == HAL_DATASPACE_JPEG_R ? mJpegRConfigurations :
mConfigurations;
for (StreamConfiguration config : configs) {
if ((config.getFormat() == internalFormat) && config.isOutput() &&
@@ -1120,6 +1167,9 @@ public final class StreamConfigurationMap {
Arrays.equals(mHeicConfigurations, other.mHeicConfigurations) &&
Arrays.equals(mHeicMinFrameDurations, other.mHeicMinFrameDurations) &&
Arrays.equals(mHeicStallDurations, other.mHeicStallDurations) &&
+ Arrays.equals(mJpegRConfigurations, other.mJpegRConfigurations) &&
+ Arrays.equals(mJpegRMinFrameDurations, other.mJpegRMinFrameDurations) &&
+ Arrays.equals(mJpegRStallDurations, other.mJpegRStallDurations) &&
Arrays.equals(mHighSpeedVideoConfigurations,
other.mHighSpeedVideoConfigurations);
}
@@ -1138,6 +1188,7 @@ public final class StreamConfigurationMap {
mDynamicDepthConfigurations, mDynamicDepthMinFrameDurations,
mDynamicDepthStallDurations, mHeicConfigurations,
mHeicMinFrameDurations, mHeicStallDurations,
+ mJpegRConfigurations, mJpegRMinFrameDurations, mJpegRStallDurations,
mHighSpeedVideoConfigurations);
}
@@ -1161,6 +1212,10 @@ public final class StreamConfigurationMap {
if (mHeicOutputFormats.indexOfKey(internalFormat) >= 0) {
return format;
}
+ } else if (internalDataspace == HAL_DATASPACE_JPEG_R) {
+ if (mJpegROutputFormats.indexOfKey(internalFormat) >= 0) {
+ return format;
+ }
} else {
if (mAllOutputFormats.indexOfKey(internalFormat) >= 0) {
return format;
@@ -1365,6 +1420,7 @@ public final class StreamConfigurationMap {
*
ImageFormat.DEPTH_POINT_CLOUD => HAL_PIXEL_FORMAT_BLOB
* ImageFormat.DEPTH_JPEG => HAL_PIXEL_FORMAT_BLOB
* ImageFormat.HEIC => HAL_PIXEL_FORMAT_BLOB
+ * ImageFormat.JPEG_R => HAL_PIXEL_FORMAT_BLOB
* ImageFormat.DEPTH16 => HAL_PIXEL_FORMAT_Y16
*
*
@@ -1391,6 +1447,7 @@ public final class StreamConfigurationMap {
case ImageFormat.DEPTH_POINT_CLOUD:
case ImageFormat.DEPTH_JPEG:
case ImageFormat.HEIC:
+ case ImageFormat.JPEG_R:
return HAL_PIXEL_FORMAT_BLOB;
case ImageFormat.DEPTH16:
return HAL_PIXEL_FORMAT_Y16;
@@ -1414,6 +1471,7 @@ public final class StreamConfigurationMap {
* ImageFormat.DEPTH16 => HAL_DATASPACE_DEPTH
* ImageFormat.DEPTH_JPEG => HAL_DATASPACE_DYNAMIC_DEPTH
* ImageFormat.HEIC => HAL_DATASPACE_HEIF
+ * ImageFormat.JPEG_R => HAL_DATASPACE_JPEG_R
* others => HAL_DATASPACE_UNKNOWN
*
*
@@ -1448,6 +1506,8 @@ public final class StreamConfigurationMap {
return HAL_DATASPACE_DYNAMIC_DEPTH;
case ImageFormat.HEIC:
return HAL_DATASPACE_HEIF;
+ case ImageFormat.JPEG_R:
+ return HAL_DATASPACE_JPEG_R;
default:
return HAL_DATASPACE_UNKNOWN;
}
@@ -1500,14 +1560,15 @@ public final class StreamConfigurationMap {
dataspace == HAL_DATASPACE_DEPTH ? mDepthOutputFormats :
dataspace == HAL_DATASPACE_DYNAMIC_DEPTH ? mDynamicDepthOutputFormats :
dataspace == HAL_DATASPACE_HEIF ? mHeicOutputFormats :
+ dataspace == HAL_DATASPACE_JPEG_R ? mJpegROutputFormats :
highRes ? mHighResOutputFormats :
mOutputFormats;
int sizesCount = formatsMap.get(format);
- if ( ((!output || (dataspace == HAL_DATASPACE_DEPTH ||
+ if ( ((!output || (dataspace == HAL_DATASPACE_DEPTH || dataspace == HAL_DATASPACE_JPEG_R ||
dataspace == HAL_DATASPACE_DYNAMIC_DEPTH ||
dataspace == HAL_DATASPACE_HEIF)) && sizesCount == 0) ||
- (output && (dataspace != HAL_DATASPACE_DEPTH &&
+ (output && (dataspace != HAL_DATASPACE_DEPTH && dataspace != HAL_DATASPACE_JPEG_R &&
dataspace != HAL_DATASPACE_DYNAMIC_DEPTH &&
dataspace != HAL_DATASPACE_HEIF) &&
mAllOutputFormats.get(format) == 0)) {
@@ -1521,11 +1582,13 @@ public final class StreamConfigurationMap {
(dataspace == HAL_DATASPACE_DEPTH) ? mDepthConfigurations :
(dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthConfigurations :
(dataspace == HAL_DATASPACE_HEIF) ? mHeicConfigurations :
+ (dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRConfigurations :
mConfigurations;
StreamConfigurationDuration[] minFrameDurations =
(dataspace == HAL_DATASPACE_DEPTH) ? mDepthMinFrameDurations :
(dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthMinFrameDurations :
(dataspace == HAL_DATASPACE_HEIF) ? mHeicMinFrameDurations :
+ (dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRMinFrameDurations :
mMinFrameDurations;
for (StreamConfiguration config : configurations) {
@@ -1555,7 +1618,7 @@ public final class StreamConfigurationMap {
// Dynamic depth streams can have both fast and also high res modes.
if ((sizeIndex != sizesCount) && (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH ||
- dataspace == HAL_DATASPACE_HEIF)) {
+ dataspace == HAL_DATASPACE_HEIF) || (dataspace == HAL_DATASPACE_JPEG_R)) {
if (sizeIndex > sizesCount) {
throw new AssertionError(
@@ -1598,6 +1661,9 @@ public final class StreamConfigurationMap {
if (mHeicOutputFormats.size() > 0) {
formats[i++] = ImageFormat.HEIC;
}
+ if (mJpegROutputFormats.size() > 0) {
+ formats[i++] = ImageFormat.JPEG_R;
+ }
}
if (formats.length != i) {
throw new AssertionError("Too few formats " + i + ", expected " + formats.length);
@@ -1644,12 +1710,14 @@ public final class StreamConfigurationMap {
(dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ?
mDynamicDepthMinFrameDurations :
(dataspace == HAL_DATASPACE_HEIF) ? mHeicMinFrameDurations :
+ (dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRMinFrameDurations :
mMinFrameDurations;
case DURATION_STALL:
return (dataspace == HAL_DATASPACE_DEPTH) ? mDepthStallDurations :
(dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthStallDurations :
(dataspace == HAL_DATASPACE_HEIF) ? mHeicStallDurations :
+ (dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRStallDurations :
mStallDurations;
default:
throw new IllegalArgumentException("duration was invalid");
@@ -1664,6 +1732,7 @@ public final class StreamConfigurationMap {
size += mDepthOutputFormats.size();
size += mDynamicDepthOutputFormats.size();
size += mHeicOutputFormats.size();
+ size += mJpegROutputFormats.size();
}
return size;
@@ -1688,6 +1757,7 @@ public final class StreamConfigurationMap {
(dataspace == HAL_DATASPACE_DEPTH) ? mDepthConfigurations :
(dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthConfigurations :
(dataspace == HAL_DATASPACE_HEIF) ? mHeicConfigurations :
+ (dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRConfigurations :
mConfigurations;
for (int i = 0; i < configurations.length; i++) {
@@ -1908,6 +1978,8 @@ public final class StreamConfigurationMap {
return "PRIVATE";
case ImageFormat.HEIC:
return "HEIC";
+ case ImageFormat.JPEG_R:
+ return "JPEG/R";
default:
return "UNKNOWN";
}
@@ -1948,6 +2020,10 @@ public final class StreamConfigurationMap {
* @hide
*/
public static final int HAL_DATASPACE_HEIF = 0x1003;
+ /**
+ * @hide
+ */
+ public static final int HAL_DATASPACE_JPEG_R = 0x1005;
private static final long DURATION_20FPS_NS = 50000000L;
/**
* @see #getDurations(int, int)
@@ -1971,6 +2047,10 @@ public final class StreamConfigurationMap {
private final StreamConfigurationDuration[] mHeicMinFrameDurations;
private final StreamConfigurationDuration[] mHeicStallDurations;
+ private final StreamConfiguration[] mJpegRConfigurations;
+ private final StreamConfigurationDuration[] mJpegRMinFrameDurations;
+ private final StreamConfigurationDuration[] mJpegRStallDurations;
+
private final HighSpeedVideoConfiguration[] mHighSpeedVideoConfigurations;
private final ReprocessFormatsMap mInputOutputFormatsMap;
@@ -1992,6 +2072,8 @@ public final class StreamConfigurationMap {
private final SparseIntArray mDynamicDepthOutputFormats = new SparseIntArray();
/** internal format -> num heic output sizes mapping, for HAL_DATASPACE_HEIF */
private final SparseIntArray mHeicOutputFormats = new SparseIntArray();
+ /** internal format -> num Jpeg/R output sizes mapping, for HAL_DATASPACE_JPEG_R */
+ private final SparseIntArray mJpegROutputFormats = new SparseIntArray();
/** High speed video Size -> FPS range count mapping*/
private final HashMap*HighSpeedVideoSize*/Size, /*Count*/Integer> mHighSpeedVideoSizeMap =
diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index 68f29278f282..88373e80240a 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -60,7 +60,8 @@ public class ImageFormat {
RAW_DEPTH,
RAW_DEPTH10,
PRIVATE,
- HEIC
+ HEIC,
+ JPEG_R
})
public @interface Format {
}
@@ -257,6 +258,14 @@ public class ImageFormat {
*/
public static final int DEPTH_JPEG = 0x69656963;
+ /**
+ * Compressed JPEG format that includes an embedded recovery map.
+ *
+ * JPEG compressed main image along with XMP embedded recovery map
+ * following ISO TBD.
+ */
+ public static final int JPEG_R = 0x1005;
+
/**
* Multi-plane Android YUV 420 format
*
@@ -886,6 +895,7 @@ public class ImageFormat {
case Y8:
case DEPTH_JPEG:
case HEIC:
+ case JPEG_R:
return true;
}
diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java
index f223bfd357d6..72aaa3554ddb 100644
--- a/media/java/android/media/ImageReader.java
+++ b/media/java/android/media/ImageReader.java
@@ -1199,6 +1199,7 @@ public class ImageReader implements AutoCloseable {
case ImageFormat.RAW_PRIVATE:
case ImageFormat.DEPTH_JPEG:
case ImageFormat.HEIC:
+ case ImageFormat.JPEG_R:
width = ImageReader.this.getWidth();
break;
default:
@@ -1217,6 +1218,7 @@ public class ImageReader implements AutoCloseable {
case ImageFormat.RAW_PRIVATE:
case ImageFormat.DEPTH_JPEG:
case ImageFormat.HEIC:
+ case ImageFormat.JPEG_R:
height = ImageReader.this.getHeight();
break;
default:
diff --git a/media/java/android/media/ImageUtils.java b/media/java/android/media/ImageUtils.java
index 2f1a36cba9d0..8f7019d4e494 100644
--- a/media/java/android/media/ImageUtils.java
+++ b/media/java/android/media/ImageUtils.java
@@ -68,6 +68,7 @@ class ImageUtils {
case ImageFormat.RAW_DEPTH10:
case ImageFormat.DEPTH_JPEG:
case ImageFormat.HEIC:
+ case ImageFormat.JPEG_R:
return 1;
case ImageFormat.PRIVATE:
return 0;
@@ -231,6 +232,7 @@ class ImageUtils {
case ImageFormat.DEPTH_POINT_CLOUD:
case ImageFormat.DEPTH_JPEG:
case ImageFormat.HEIC:
+ case ImageFormat.JPEG_R:
estimatedBytePerPixel = 0.3;
break;
case ImageFormat.Y8:
@@ -304,6 +306,7 @@ class ImageUtils {
case ImageFormat.RAW_DEPTH:
case ImageFormat.RAW_DEPTH10:
case ImageFormat.HEIC:
+ case ImageFormat.JPEG_R:
return new Size(image.getWidth(), image.getHeight());
case ImageFormat.PRIVATE:
return new Size(0, 0);
--
cgit v1.2.3-59-g8ed1b
From 8bbaeb7a49c8961d55d6343a5a7f7752b031087a Mon Sep 17 00:00:00 2001
From: Seigo Nonaka
Date: Tue, 20 Dec 2022 23:00:08 +0900
Subject: Fix performance regression of system_server boot time
The hash calculation of FontFamily took a time. In this case, just
using identity hash should be fine.
Bug: 263015762
Test: atest TypefaceSystemFallbackTest FontListParserTest
Test: atest UpdatableFontDirTest UpdatableSystemFontTest
Test: atest FontManagerTest
Change-Id: Id8c39e646f92977867c82a1effb93c925c909ef2
---
graphics/java/android/graphics/fonts/SystemFonts.java | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
(limited to 'graphics/java')
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
index ec8b2d66da7f..8fe28ae731b8 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -22,8 +22,8 @@ import android.graphics.FontListParser;
import android.graphics.Typeface;
import android.text.FontConfig;
import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.Log;
+import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -117,17 +117,18 @@ public final class SystemFonts {
}
}
+
final FontFamily defaultFamily = defaultFonts.isEmpty() ? null : createFontFamily(
defaultFonts, languageTags, variant, false, cache);
-
// Insert family into fallback map.
for (int i = 0; i < fallbackMap.size(); i++) {
final String name = fallbackMap.keyAt(i);
final NativeFamilyListSet familyListSet = fallbackMap.valueAt(i);
- if (familyListSet.seenXmlFamilies.contains(xmlFamily)) {
+ int identityHash = System.identityHashCode(xmlFamily);
+ if (familyListSet.seenXmlFamilies.get(identityHash, -1) != -1) {
continue;
} else {
- familyListSet.seenXmlFamilies.add(xmlFamily);
+ familyListSet.seenXmlFamilies.append(identityHash, 1);
}
final ArrayList fallback = specificFallbackFonts.get(name);
if (fallback == null) {
@@ -213,7 +214,7 @@ public final class SystemFonts {
return;
}
familyListSet.familyList.add(family);
- familyListSet.seenXmlFamilies.add(xmlFamily);
+ familyListSet.seenXmlFamilies.append(System.identityHashCode(xmlFamily), 1);
}
fallbackListMap.put(familyName, familyListSet);
}
@@ -276,7 +277,7 @@ public final class SystemFonts {
private static final class NativeFamilyListSet {
public List familyList = new ArrayList<>();
- public Set seenXmlFamilies = new ArraySet<>();
+ public SparseIntArray seenXmlFamilies = new SparseIntArray();
}
/** @hide */
--
cgit v1.2.3-59-g8ed1b
From 12d371af853554e30905e0b7922e9d6912e2efab Mon Sep 17 00:00:00 2001
From: Sally Qi
Date: Wed, 26 Oct 2022 17:30:26 -0700
Subject: Add BT2020_HLG, BT2020_PQ to ColorSpace.Named enum
- Reuse transferParameters class to initialize non ICC curve ColorSpace,
e.g., BT2020_HLG, BT2020_PQ.
- Add back-compactability between skia and java side for HLG/PQ.
- lift up the restriction that BitmapFactory#isPreferredColorSpace is
ICC parametric curve.
Bug: 241284309
Test: android.graphics.cts.ColorSpaceTest,
android.graphics.cts.BitmapTest
Change-Id: Ib8d0f53fa4dc71cbd0f4eb239564c8d21f1e2ba7
---
core/api/current.txt | 2 +
core/java/android/hardware/DataSpace.java | 4 +-
graphics/java/android/graphics/BitmapFactory.java | 16 +-
graphics/java/android/graphics/ColorSpace.java | 247 ++++++++++++++++------
libs/hwui/jni/Graphics.cpp | 28 ++-
5 files changed, 222 insertions(+), 75 deletions(-)
(limited to 'graphics/java')
diff --git a/core/api/current.txt b/core/api/current.txt
index 877e2d6b54b4..ac3f95a036b3 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -14831,6 +14831,8 @@ package android.graphics {
enum_constant public static final android.graphics.ColorSpace.Named ACESCG;
enum_constant public static final android.graphics.ColorSpace.Named ADOBE_RGB;
enum_constant public static final android.graphics.ColorSpace.Named BT2020;
+ enum_constant public static final android.graphics.ColorSpace.Named BT2020_HLG;
+ enum_constant public static final android.graphics.ColorSpace.Named BT2020_PQ;
enum_constant public static final android.graphics.ColorSpace.Named BT709;
enum_constant public static final android.graphics.ColorSpace.Named CIE_LAB;
enum_constant public static final android.graphics.ColorSpace.Named CIE_XYZ;
diff --git a/core/java/android/hardware/DataSpace.java b/core/java/android/hardware/DataSpace.java
index 15eae0920e7d..7372fb72fab5 100644
--- a/core/java/android/hardware/DataSpace.java
+++ b/core/java/android/hardware/DataSpace.java
@@ -508,9 +508,7 @@ public final class DataSpace {
public static final int DATASPACE_BT2020_HLG = 168165376;
/**
- * ITU-R Recommendation 2020 (BT.2020)
- *
- * Ultra High-definition television.
+ * Perceptual Quantizer encoding.
*
* Composed of the following -
*
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index ef1e7bfc6651..701e20c499da 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -161,11 +161,17 @@ public class BitmapFactory {
* be thrown by the decode methods when setting a non-RGB color space
* such as {@link ColorSpace.Named#CIE_LAB Lab}.
*
- * The specified color space's transfer function must be
+ *
+ * Prior to {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * the specified color space's transfer function must be
* an {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}. An
* IllegalArgumentException will be thrown by the decode methods
* if calling {@link ColorSpace.Rgb#getTransferParameters()} on the
- * specified color space returns null.
+ * specified color space returns null.
+ *
+ * Starting from {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * non ICC parametric curve transfer function is allowed.
+ * E.g., {@link ColorSpace.Named#BT2020_HLG BT2020_HLG}.
*
* After decode, the bitmap's color space is stored in
* {@link #outColorSpace}.
@@ -458,7 +464,11 @@ public class BitmapFactory {
throw new IllegalArgumentException("The destination color space must use the " +
"RGB color model");
}
- if (((ColorSpace.Rgb) opts.inPreferredColorSpace).getTransferParameters() == null) {
+ if (!opts.inPreferredColorSpace.equals(ColorSpace.get(ColorSpace.Named.BT2020_HLG))
+ && !opts.inPreferredColorSpace.equals(
+ ColorSpace.get(ColorSpace.Named.BT2020_PQ))
+ && ((ColorSpace.Rgb) opts.inPreferredColorSpace)
+ .getTransferParameters() == null) {
throw new IllegalArgumentException("The destination color space must use an " +
"ICC parametric transfer function");
}
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index 31df474eb10c..2427dec169d6 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -199,6 +199,8 @@ public abstract class ColorSpace {
private static final float[] SRGB_PRIMARIES = { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f };
private static final float[] NTSC_1953_PRIMARIES = { 0.67f, 0.33f, 0.21f, 0.71f, 0.14f, 0.08f };
+ private static final float[] BT2020_PRIMARIES =
+ { 0.708f, 0.292f, 0.170f, 0.797f, 0.131f, 0.046f };
/**
* A gray color space does not have meaningful primaries, so we use this arbitrary set.
*/
@@ -208,6 +210,12 @@ public abstract class ColorSpace {
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);
+ private static final Rgb.TransferParameters BT2020_HLG_TRANSFER_PARAMETERS =
+ new Rgb.TransferParameters(2.0f, 2.0f, 1 / 0.17883277f,
+ 0.28466892f, 0.5599107f, 0.0f, -3.0f, true);
+ private static final Rgb.TransferParameters BT2020_PQ_TRANSFER_PARAMETERS =
+ new Rgb.TransferParameters(107 / 128.0f, 1.0f, 32 / 2523.0f,
+ 2413 / 128.0f, -2392 / 128.0f, 8192 / 1305.0f, -2.0f, true);
// See static initialization block next to #get(Named)
private static final ColorSpace[] sNamedColorSpaces = new ColorSpace[Named.values().length];
@@ -703,7 +711,29 @@ public abstract class ColorSpace {
* | Range | \(L: [0.0, 100.0], a: [-128, 128], b: [-128, 128]\) |
*
*/
- CIE_LAB
+ CIE_LAB,
+ /**
+ * {@link ColorSpace.Rgb RGB} color space BT.2100 standardized as
+ * Hybrid Log Gamma encoding.
+ *
+ * | Property | Value |
+ * | Name | Hybrid Log Gamma encoding |
+ * | CIE standard illuminant | D65 |
+ * | Range | \([0..1]\) |
+ *
+ */
+ BT2020_HLG,
+ /**
+ * {@link ColorSpace.Rgb RGB} color space BT.2100 standardized as
+ * Perceptual Quantizer encoding.
+ *
+ * | Property | Value |
+ * | Name | Perceptual Quantizer encoding |
+ * | CIE standard illuminant | D65 |
+ * | Range | \([0..1]\) |
+ *
+ */
+ BT2020_PQ
// Update the initialization block next to #get(Named) when adding new values
}
@@ -1534,7 +1564,7 @@ public abstract class ColorSpace {
sDataToColorSpaces.put(DataSpace.DATASPACE_BT709, Named.BT709.ordinal());
sNamedColorSpaces[Named.BT2020.ordinal()] = new ColorSpace.Rgb(
"Rec. ITU-R BT.2020-1",
- new float[] { 0.708f, 0.292f, 0.170f, 0.797f, 0.131f, 0.046f },
+ BT2020_PRIMARIES,
ILLUMINANT_D65,
null,
new Rgb.TransferParameters(1 / 1.0993, 0.0993 / 1.0993, 1 / 4.5, 0.08145, 1 / 0.45),
@@ -1616,6 +1646,70 @@ public abstract class ColorSpace {
"Generic L*a*b*",
Named.CIE_LAB.ordinal()
);
+ sNamedColorSpaces[Named.BT2020_HLG.ordinal()] = new ColorSpace.Rgb(
+ "Hybrid Log Gamma encoding",
+ BT2020_PRIMARIES,
+ ILLUMINANT_D65,
+ null,
+ x -> transferHLGOETF(x),
+ x -> transferHLGEOTF(x),
+ 0.0f, 1.0f,
+ BT2020_HLG_TRANSFER_PARAMETERS,
+ Named.BT2020_HLG.ordinal()
+ );
+ sDataToColorSpaces.put(DataSpace.DATASPACE_BT2020_HLG, Named.BT2020_HLG.ordinal());
+ sNamedColorSpaces[Named.BT2020_PQ.ordinal()] = new ColorSpace.Rgb(
+ "Perceptual Quantizer encoding",
+ BT2020_PRIMARIES,
+ ILLUMINANT_D65,
+ null,
+ x -> transferST2048OETF(x),
+ x -> transferST2048EOTF(x),
+ 0.0f, 1.0f,
+ BT2020_PQ_TRANSFER_PARAMETERS,
+ Named.BT2020_PQ.ordinal()
+ );
+ sDataToColorSpaces.put(DataSpace.DATASPACE_BT2020_PQ, Named.BT2020_PQ.ordinal());
+ }
+
+ private static double transferHLGOETF(double x) {
+ double a = 0.17883277;
+ double b = 0.28466892;
+ double c = 0.55991073;
+ double r = 0.5;
+ return x > 1.0 ? a * Math.log(x - b) + c : r * Math.sqrt(x);
+ }
+
+ private static double transferHLGEOTF(double x) {
+ double a = 0.17883277;
+ double b = 0.28466892;
+ double c = 0.55991073;
+ double r = 0.5;
+ return x <= 0.5 ? (x * x) / (r * r) : Math.exp((x - c) / a + b);
+ }
+
+ private static double transferST2048OETF(double x) {
+ double m1 = (2610.0 / 4096.0) / 4.0;
+ double m2 = (2523.0 / 4096.0) * 128.0;
+ double c1 = (3424.0 / 4096.0);
+ double c2 = (2413.0 / 4096.0) * 32.0;
+ double c3 = (2392.0 / 4096.0) * 32.0;
+
+ double tmp = Math.pow(x, m1);
+ tmp = (c1 + c2 * tmp) / (1.0 + c3 * tmp);
+ return Math.pow(tmp, m2);
+ }
+
+ private static double transferST2048EOTF(double x) {
+ double m1 = (2610.0 / 4096.0) / 4.0;
+ double m2 = (2523.0 / 4096.0) * 128.0;
+ double c1 = (3424.0 / 4096.0);
+ double c2 = (2413.0 / 4096.0) * 32.0;
+ double c3 = (2392.0 / 4096.0) * 32.0;
+
+ double tmp = Math.pow(Math.min(Math.max(x, 0.0), 1.0), 1.0 / m2);
+ tmp = Math.max(tmp - c1, 0.0) / (c2 - c3 * tmp);
+ return Math.pow(tmp, 1.0 / m1);
}
// Reciprocal piecewise gamma response
@@ -2197,6 +2291,58 @@ public abstract class ColorSpace {
/** Variable \(g\) in the equation of the EOTF described above. */
public final double g;
+ private TransferParameters(double a, double b, double c, double d, double e,
+ double f, double g, boolean nonCurveTransferParameters) {
+ // nonCurveTransferParameters correspondes to a "special" transfer function
+ if (!nonCurveTransferParameters) {
+ if (Double.isNaN(a) || Double.isNaN(b) || Double.isNaN(c)
+ || Double.isNaN(d) || Double.isNaN(e) || Double.isNaN(f)
+ || Double.isNaN(g)) {
+ throw new IllegalArgumentException("Parameters cannot be NaN");
+ }
+
+ // Next representable float after 1.0
+ // We use doubles here but the representation inside our native code
+ // is often floats
+ if (!(d >= 0.0 && d <= 1.0f + Math.ulp(1.0f))) {
+ throw new IllegalArgumentException(
+ "Parameter d must be in the range [0..1], " + "was " + d);
+ }
+
+ if (d == 0.0 && (a == 0.0 || g == 0.0)) {
+ throw new IllegalArgumentException(
+ "Parameter a or g is zero, the transfer function is constant");
+ }
+
+ if (d >= 1.0 && c == 0.0) {
+ throw new IllegalArgumentException(
+ "Parameter c is zero, the transfer function is constant");
+ }
+
+ if ((a == 0.0 || g == 0.0) && c == 0.0) {
+ throw new IllegalArgumentException("Parameter a or g is zero,"
+ + " and c is zero, the transfer function is constant");
+ }
+
+ if (c < 0.0) {
+ throw new IllegalArgumentException(
+ "The transfer function must be increasing");
+ }
+
+ if (a < 0.0 || g < 0.0) {
+ throw new IllegalArgumentException(
+ "The transfer function must be positive or increasing");
+ }
+ }
+ this.a = a;
+ this.b = b;
+ this.c = c;
+ this.d = d;
+ this.e = e;
+ this.f = f;
+ this.g = g;
+ }
+
/**
* Defines the parameters for the ICC parametric curve type 3, as
* defined in ICC.1:2004-10, section 10.15.
@@ -2219,7 +2365,7 @@ public abstract class ColorSpace {
* @throws IllegalArgumentException If the parameters form an invalid transfer function
*/
public TransferParameters(double a, double b, double c, double d, double g) {
- this(a, b, c, d, 0.0, 0.0, g);
+ this(a, b, c, d, 0.0, 0.0, g, false);
}
/**
@@ -2238,51 +2384,7 @@ public abstract class ColorSpace {
*/
public TransferParameters(double a, double b, double c, double d, double e,
double f, double g) {
-
- if (Double.isNaN(a) || Double.isNaN(b) || Double.isNaN(c) ||
- Double.isNaN(d) || Double.isNaN(e) || Double.isNaN(f) ||
- Double.isNaN(g)) {
- throw new IllegalArgumentException("Parameters cannot be NaN");
- }
-
- // Next representable float after 1.0
- // We use doubles here but the representation inside our native code is often floats
- if (!(d >= 0.0 && d <= 1.0f + Math.ulp(1.0f))) {
- throw new IllegalArgumentException("Parameter d must be in the range [0..1], " +
- "was " + d);
- }
-
- if (d == 0.0 && (a == 0.0 || g == 0.0)) {
- throw new IllegalArgumentException(
- "Parameter a or g is zero, the transfer function is constant");
- }
-
- if (d >= 1.0 && c == 0.0) {
- throw new IllegalArgumentException(
- "Parameter c is zero, the transfer function is constant");
- }
-
- if ((a == 0.0 || g == 0.0) && c == 0.0) {
- throw new IllegalArgumentException("Parameter a or g is zero," +
- " and c is zero, the transfer function is constant");
- }
-
- if (c < 0.0) {
- throw new IllegalArgumentException("The transfer function must be increasing");
- }
-
- if (a < 0.0 || g < 0.0) {
- throw new IllegalArgumentException("The transfer function must be " +
- "positive or increasing");
- }
-
- this.a = a;
- this.b = b;
- this.c = c;
- this.d = d;
- this.e = e;
- this.f = f;
- this.g = g;
+ this(a, b, c, d, e, f, g, false);
}
@SuppressWarnings("SimplifiableIfStatement")
@@ -2357,6 +2459,36 @@ public abstract class ColorSpace {
private static native long nativeCreate(float a, float b, float c, float d,
float e, float f, float g, float[] xyz);
+ private static DoubleUnaryOperator generateOETF(TransferParameters function) {
+ boolean isNonCurveTransferParameters = function.equals(BT2020_HLG_TRANSFER_PARAMETERS)
+ || function.equals(BT2020_PQ_TRANSFER_PARAMETERS);
+ if (isNonCurveTransferParameters) {
+ return function.f == 0.0 && function.g < 0.0 ? x -> transferHLGOETF(x)
+ : x -> transferST2048OETF(x);
+ } else {
+ return function.e == 0.0 && function.f == 0.0
+ ? x -> rcpResponse(x, function.a, function.b,
+ function.c, function.d, function.g)
+ : x -> rcpResponse(x, function.a, function.b, function.c,
+ function.d, function.e, function.f, function.g);
+ }
+ }
+
+ private static DoubleUnaryOperator generateEOTF(TransferParameters function) {
+ boolean isNonCurveTransferParameters = function.equals(BT2020_HLG_TRANSFER_PARAMETERS)
+ || function.equals(BT2020_PQ_TRANSFER_PARAMETERS);
+ if (isNonCurveTransferParameters) {
+ return function.f == 0.0 && function.g < 0.0 ? x -> transferHLGEOTF(x)
+ : x -> transferST2048EOTF(x);
+ } else {
+ return function.e == 0.0 && function.f == 0.0
+ ? x -> response(x, function.a, function.b,
+ function.c, function.d, function.g)
+ : x -> response(x, function.a, function.b, function.c,
+ function.d, function.e, function.f, function.g);
+ }
+ }
+
/**
* Creates a new RGB color space using a 3x3 column-major transform matrix.
* The transform matrix must convert from the RGB space to the profile connection
@@ -2553,16 +2685,8 @@ public abstract class ColorSpace {
@NonNull TransferParameters function,
@IntRange(from = MIN_ID, to = MAX_ID) int id) {
this(name, primaries, whitePoint, transform,
- function.e == 0.0 && function.f == 0.0 ?
- x -> rcpResponse(x, function.a, function.b,
- function.c, function.d, function.g) :
- x -> rcpResponse(x, function.a, function.b, function.c,
- function.d, function.e, function.f, function.g),
- function.e == 0.0 && function.f == 0.0 ?
- x -> response(x, function.a, function.b,
- function.c, function.d, function.g) :
- x -> response(x, function.a, function.b, function.c,
- function.d, function.e, function.f, function.g),
+ generateOETF(function),
+ generateEOTF(function),
0.0f, 1.0f, function, id);
}
@@ -3063,7 +3187,12 @@ public abstract class ColorSpace {
*/
@Nullable
public TransferParameters getTransferParameters() {
- return mTransferParameters;
+ if (mTransferParameters != null
+ && !mTransferParameters.equals(BT2020_PQ_TRANSFER_PARAMETERS)
+ && !mTransferParameters.equals(BT2020_HLG_TRANSFER_PARAMETERS)) {
+ return mTransferParameters;
+ }
+ return null;
}
@Override
diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp
index 6a3bc8fe1152..c8358497ad62 100644
--- a/libs/hwui/jni/Graphics.cpp
+++ b/libs/hwui/jni/Graphics.cpp
@@ -576,14 +576,22 @@ jobject GraphicsJNI::getColorSpace(JNIEnv* env, SkColorSpace* decodeColorSpace,
LOG_ALWAYS_FATAL_IF(!decodeColorSpace->toXYZD50(&xyzMatrix));
skcms_TransferFunction transferParams;
- // We can only handle numerical transfer functions at the moment
- LOG_ALWAYS_FATAL_IF(!decodeColorSpace->isNumericalTransferFn(&transferParams));
-
- jobject params = env->NewObject(gTransferParameters_class,
- gTransferParameters_constructorMethodID,
- transferParams.a, transferParams.b, transferParams.c,
- transferParams.d, transferParams.e, transferParams.f,
- transferParams.g);
+ decodeColorSpace->transferFn(&transferParams);
+ auto res = skcms_TransferFunction_getType(&transferParams);
+ LOG_ALWAYS_FATAL_IF(res == skcms_TFType_HLGinvish || res == skcms_TFType_Invalid);
+
+ jobject params;
+ if (res == skcms_TFType_PQish || res == skcms_TFType_HLGish) {
+ params = env->NewObject(gTransferParameters_class, gTransferParameters_constructorMethodID,
+ transferParams.a, transferParams.b, transferParams.c,
+ transferParams.d, transferParams.e, transferParams.f,
+ transferParams.g, true);
+ } else {
+ params = env->NewObject(gTransferParameters_class, gTransferParameters_constructorMethodID,
+ transferParams.a, transferParams.b, transferParams.c,
+ transferParams.d, transferParams.e, transferParams.f,
+ transferParams.g, false);
+ }
jfloatArray xyzArray = env->NewFloatArray(9);
jfloat xyz[9] = {
@@ -808,8 +816,8 @@ int register_android_graphics_Graphics(JNIEnv* env)
gTransferParameters_class = MakeGlobalRefOrDie(env, FindClassOrDie(env,
"android/graphics/ColorSpace$Rgb$TransferParameters"));
- gTransferParameters_constructorMethodID = GetMethodIDOrDie(env, gTransferParameters_class,
- "", "(DDDDDDD)V");
+ gTransferParameters_constructorMethodID =
+ GetMethodIDOrDie(env, gTransferParameters_class, "", "(DDDDDDDZ)V");
gFontMetrics_class = FindClassOrDie(env, "android/graphics/Paint$FontMetrics");
gFontMetrics_class = MakeGlobalRefOrDie(env, gFontMetrics_class);
--
cgit v1.2.3-59-g8ed1b
From 4a39f27868d094a1b5c26bb7c531cfab436b0c10 Mon Sep 17 00:00:00 2001
From: Angel Aguayo
Date: Wed, 30 Nov 2022 19:34:30 +0000
Subject: Updated Mesh documentation
Updated Mesh and MeshSpecification documentation to better describe parameters/important method features.
Bug: b/253321460
Test: none
Change-Id: I87fb2cdc2ca36f891c339c77b2ed05dee4b84fee
---
graphics/java/android/graphics/Mesh.java | 83 ++++++++++++++++------
.../java/android/graphics/MeshSpecification.java | 81 +++++++++++++++++----
2 files changed, 131 insertions(+), 33 deletions(-)
(limited to 'graphics/java')
diff --git a/graphics/java/android/graphics/Mesh.java b/graphics/java/android/graphics/Mesh.java
index f32e0ee0cc79..1f693166ecf0 100644
--- a/graphics/java/android/graphics/Mesh.java
+++ b/graphics/java/android/graphics/Mesh.java
@@ -29,7 +29,8 @@ import java.nio.ShortBuffer;
* {@link #makeIndexed(MeshSpecification, Mode, Buffer, int, ShortBuffer, Rect)} methods,
* where a {@link MeshSpecification} is required along with various attributes for
* detailing the mesh object, including a mode, vertex buffer, optional index buffer, and bounds
- * for the mesh.
+ * for the mesh. Once generated, a mesh object can be drawn through
+ * {@link Canvas#drawMesh(Mesh, BlendMode, Paint)}
*
* @hide
*/
@@ -53,8 +54,12 @@ public class Mesh {
*
* @param meshSpec {@link MeshSpecification} used when generating the mesh.
* @param mode {@link Mode} enum
- * @param vertexBuffer vertex buffer representing through {@link Buffer}.
- * @param vertexCount the number of vertices represented in the vertexBuffer.
+ * @param vertexBuffer vertex buffer representing through {@link Buffer}. This provides the data
+ * for all attributes provided within the meshSpec for every vertex. That
+ * is, a vertex buffer should be (attributes size * number of vertices) in
+ * length to be valid. Note that currently implementation will have a CPU
+ * backed buffer generated.
+ * @param vertexCount the number of vertices represented in the vertexBuffer and mesh.
* @param bounds bounds of the mesh object.
* @return a new Mesh object.
*/
@@ -70,13 +75,20 @@ public class Mesh {
}
/**
- * Generates an indexed {@link Mesh} object.
+ * Generates a {@link Mesh} object.
*
* @param meshSpec {@link MeshSpecification} used when generating the mesh.
* @param mode {@link Mode} enum
- * @param vertexBuffer vertex buffer representing through {@link Buffer}.
- * @param vertexCount the number of vertices represented in the vertexBuffer.
- * @param indexBuffer index buffer representing through {@link ShortBuffer}.
+ * @param vertexBuffer vertex buffer representing through {@link Buffer}. This provides the data
+ * for all attributes provided within the meshSpec for every vertex. That
+ * is, a vertex buffer should be (attributes size * number of vertices) in
+ * length to be valid. Note that currently implementation will have a CPU
+ * backed buffer generated.
+ * @param vertexCount the number of vertices represented in the vertexBuffer and mesh.
+ * @param indexBuffer index buffer representing through {@link ShortBuffer}. Indices are
+ * required to be 16 bits, so ShortBuffer is necessary. Note that
+ * currently implementation will have a CPU
+ * backed buffer generated.
* @param bounds bounds of the mesh object.
* @return a new Mesh object.
*/
@@ -93,7 +105,10 @@ public class Mesh {
}
/**
- * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ * Sets the uniform color value corresponding to the shader assigned to the mesh. If the shader
+ * does not have a uniform with that name or if the uniform is declared with a type other than
+ * vec3 or vec4 and corresponding layout(color) annotation then an IllegalArgumentExcepton is
+ * thrown.
*
* @param uniformName name matching the color uniform declared in the shader program.
* @param color the provided sRGB color will be converted into the shader program's output
@@ -104,7 +119,10 @@ public class Mesh {
}
/**
- * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ * Sets the uniform color value corresponding to the shader assigned to the mesh. If the shader
+ * does not have a uniform with that name or if the uniform is declared with a type other than
+ * vec3 or vec4 and corresponding layout(color) annotation then an IllegalArgumentExcepton is
+ * thrown.
*
* @param uniformName name matching the color uniform declared in the shader program.
* @param color the provided sRGB color will be converted into the shader program's output
@@ -116,7 +134,10 @@ public class Mesh {
}
/**
- * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ * Sets the uniform color value corresponding to the shader assigned to the mesh. If the shader
+ * does not have a uniform with that name or if the uniform is declared with a type other than
+ * vec3 or vec4 and corresponding layout(color) annotation then an IllegalArgumentExcepton is
+ * thrown.
*
* @param uniformName name matching the color uniform declared in the shader program.
* @param color the provided sRGB color will be converted into the shader program's output
@@ -132,7 +153,9 @@ public class Mesh {
}
/**
- * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does
+ * not have a uniform with that name or if the uniform is declared with a type other than a
+ * float or float[1] then an IllegalArgumentException is thrown.
*
* @param uniformName name matching the float uniform declared in the shader program.
* @param value float value corresponding to the float uniform with the given name.
@@ -142,7 +165,9 @@ public class Mesh {
}
/**
- * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does
+ * not have a uniform with that name or if the uniform is declared with a type other than a
+ * vec2 or float[2] then an IllegalArgumentException is thrown.
*
* @param uniformName name matching the float uniform declared in the shader program.
* @param value1 first float value corresponding to the float uniform with the given name.
@@ -153,7 +178,9 @@ public class Mesh {
}
/**
- * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does
+ * not have a uniform with that name or if the uniform is declared with a type other than a
+ * vec3 or float[3] then an IllegalArgumentException is thrown.
*
* @param uniformName name matching the float uniform declared in the shader program.
* @param value1 first float value corresponding to the float uniform with the given name.
@@ -166,7 +193,9 @@ public class Mesh {
}
/**
- * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does
+ * not have a uniform with that name or if the uniform is declared with a type other than a
+ * vec4 or float[4] then an IllegalArgumentException is thrown.
*
* @param uniformName name matching the float uniform declared in the shader program.
* @param value1 first float value corresponding to the float uniform with the given name.
@@ -180,7 +209,10 @@ public class Mesh {
}
/**
- * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does
+ * not have a uniform with that name or if the uniform is declared with a type other than a
+ * float (for N=1), vecN, or float[N], where N is the length of the values param, then an
+ * IllegalArgumentException is thrown.
*
* @param uniformName name matching the float uniform declared in the shader program.
* @param values float value corresponding to the vec4 float uniform with the given name.
@@ -210,7 +242,9 @@ public class Mesh {
}
/**
- * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does
+ * not have a uniform with that name or if the uniform is declared with a type other than int
+ * or int[1] then an IllegalArgumentException is thrown.
*
* @param uniformName name matching the int uniform delcared in the shader program.
* @param value value corresponding to the int uniform with the given name.
@@ -220,7 +254,9 @@ public class Mesh {
}
/**
- * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does
+ * not have a uniform with that name or if the uniform is declared with a type other than ivec2
+ * or int[2] then an IllegalArgumentException is thrown.
*
* @param uniformName name matching the int uniform delcared in the shader program.
* @param value1 first value corresponding to the int uniform with the given name.
@@ -231,7 +267,9 @@ public class Mesh {
}
/**
- * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does
+ * not have a uniform with that name or if the uniform is declared with a type other than ivec3
+ * or int[3] then an IllegalArgumentException is thrown.
*
* @param uniformName name matching the int uniform delcared in the shader program.
* @param value1 first value corresponding to the int uniform with the given name.
@@ -243,7 +281,9 @@ public class Mesh {
}
/**
- * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does
+ * not have a uniform with that name or if the uniform is declared with a type other than ivec4
+ * or int[4] then an IllegalArgumentException is thrown.
*
* @param uniformName name matching the int uniform delcared in the shader program.
* @param value1 first value corresponding to the int uniform with the given name.
@@ -256,7 +296,10 @@ public class Mesh {
}
/**
- * Sets the uniform color value corresponding to the shader assigned to the mesh.
+ * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does
+ * not have a uniform with that name or if the uniform is declared with a type other than an
+ * int (for N=1), ivecN, or int[N], where N is the length of the values param, then an
+ * IllegalArgumentException is thrown.
*
* @param uniformName name matching the int uniform delcared in the shader program.
* @param values int values corresponding to the vec4 int uniform with the given name.
diff --git a/graphics/java/android/graphics/MeshSpecification.java b/graphics/java/android/graphics/MeshSpecification.java
index 45c13affac14..dd8fb7ab2370 100644
--- a/graphics/java/android/graphics/MeshSpecification.java
+++ b/graphics/java/android/graphics/MeshSpecification.java
@@ -25,7 +25,7 @@ import libcore.util.NativeAllocationRegistry;
* generates a {@link MeshSpecification} via the Make method, where multiple parameters to set up
* the mesh are supplied, including attributes, vertex stride, varyings, and
* vertex/fragment shaders. There are also additional methods to provide an optional
- * {@link ColorSpace} as well as an alpha type.
+ * {@link ColorSpace} as well as an {@link AlphaType}.
*
* Note that there are several limitations on various mesh specifications:
* 1. The max amount of attributes allowed is 8.
@@ -43,15 +43,30 @@ public class MeshSpecification {
/**
* Constants for {@link #make(Attribute[], int, Varying[], String, String, ColorSpace, int)}
- * to determine alpha type
+ * to determine alpha type. Describes how to interpret the alpha component of a pixel.
*/
@IntDef({UNKNOWN, OPAQUE, PREMUL, UNPREMULT})
public @interface AlphaType {
}
+ /**
+ * uninitialized.
+ */
public static final int UNKNOWN = 0;
+
+ /**
+ * Pixel is opaque.
+ */
public static final int OPAQUE = 1;
+
+ /**
+ * Pixel components are premultiplied by alpha.
+ */
public static final int PREMUL = 2;
+
+ /**
+ * Pixel components are independent of alpha.
+ */
public static final int UNPREMULT = 3;
/**
@@ -61,15 +76,41 @@ public class MeshSpecification {
public @interface Type {
}
+ /**
+ * Represents one float. Its equivalent shader type is float.
+ */
public static final int FLOAT = 0;
+
+ /**
+ * Represents two floats. Its equivalent shader type is float2.
+ */
public static final int FLOAT2 = 1;
+
+ /**
+ * Represents three floats. Its equivalent shader type is float3.
+ */
public static final int FLOAT3 = 2;
+
+ /**
+ * Represents four floats. Its equivalent shader type is float4.
+ */
public static final int FLOAT4 = 3;
+
+ /**
+ * Represents four bytes. Its equivalent shader type is half4.
+ */
public static final int UBYTE4 = 4;
/**
* Data class to represent a single attribute in a shader. Note that type parameter must be
* one of {@link #FLOAT}, {@link #FLOAT2}, {@link #FLOAT3}, {@link #FLOAT4}, or {@link #UBYTE4}.
+ *
+ * Note that offset is the offset in number of bytes. For example, if we had two attributes
+ *
+ * Float3 att1
+ * Float att2
+ *
+ * att1 would have an offset of 0, while att2 would have an offset of 12 bytes.
*/
public static class Attribute {
@Type
@@ -106,14 +147,19 @@ public class MeshSpecification {
}
/**
- * Creates a {@link MeshSpecification} object.
+ * Creates a {@link MeshSpecification} object for use within {@link Mesh}.
*
* @param attributes list of attributes represented by {@link Attribute}. Can hold a max of
* 8.
- * @param vertexStride length of vertex stride. Max of 1024 is accepted.
+ * @param vertexStride length of vertex stride in bytes. This should be the size of a single
+ * vertex' attributes. Max of 1024 is accepted.
* @param varyings List of varyings represented by {@link Varying}. Can hold a max of 6.
- * @param vertexShader vertex shader to be supplied to the mesh.
- * @param fragmentShader fragment shader to be suppied to the mesh.
+ * Note that `position` is provided by default, does not need to be
+ * provided in the list, and does not count towards
+ * the 6 varyings allowed.
+ * @param vertexShader vertex shader to be supplied to the mesh. Ensure that the position
+ * varying is set within the shader to get proper results.
+ * @param fragmentShader fragment shader to be supplied to the mesh.
* @return {@link MeshSpecification} object for use when creating {@link Mesh}
*/
public static MeshSpecification make(Attribute[] attributes, int vertexStride,
@@ -131,10 +177,14 @@ public class MeshSpecification {
*
* @param attributes list of attributes represented by {@link Attribute}. Can hold a max of
* 8.
- * @param vertexStride length of vertex stride. Max of 1024 is accepted.
- * @param varyings List of varyings represented by {@link Varying}. Can hold a max of
- * 6.
- * @param vertexShader vertex shader to be supplied to the mesh.
+ * @param vertexStride length of vertex stride in bytes. This should be the size of a single
+ * vertex' attributes. Max of 1024 is accepted.
+ * @param varyings List of varyings represented by {@link Varying}. Can hold a max of 6.
+ * Note that `position` is provided by default, does not need to be
+ * provided in the list, and does not count towards
+ * the 6 varyings allowed.
+ * @param vertexShader vertex shader to be supplied to the mesh. Ensure that the position
+ * varying is set within the shader to get proper results.
* @param fragmentShader fragment shader to be supplied to the mesh.
* @param colorSpace {@link ColorSpace} to tell what color space to work in.
* @return {@link MeshSpecification} object for use when creating {@link Mesh}
@@ -154,10 +204,15 @@ public class MeshSpecification {
*
* @param attributes list of attributes represented by {@link Attribute}. Can hold a max of
* 8.
- * @param vertexStride length of vertex stride. Max of 1024 is accepted.
+ * @param vertexStride length of vertex stride in bytes. This should be the size of a single
+ * vertex' attributes. Max of 1024 is accepted.
* @param varyings List of varyings represented by {@link Varying}. Can hold a max of 6.
- * @param vertexShader vertex shader code to be supplied to the mesh.
- * @param fragmentShader fragment shader code to be suppied to the mesh.
+ * Note that `position` is provided by default, does not need to be
+ * provided in the list, and does not count towards
+ * the 6 varyings allowed.
+ * @param vertexShader vertex shader to be supplied to the mesh. Ensure that the position
+ * varying is set within the shader to get proper results.
+ * @param fragmentShader fragment shader to be supplied to the mesh.
* @param colorSpace {@link ColorSpace} to tell what color space to work in.
* @param alphaType Describes how to interpret the alpha component for a pixel. Must be
* one of {@link AlphaType} values.
--
cgit v1.2.3-59-g8ed1b
From c321395b106b863720355050fb6d5e3a208926a0 Mon Sep 17 00:00:00 2001
From: Angel Aguayo
Date: Fri, 2 Dec 2022 01:39:03 +0000
Subject: Prepare for publicly exposing drawMesh API
Fixes API issues to keep it consistent
with the framework.
Bug: b/253321460, b/261768939
Test: HwAccelerationTest MeshActivity
Change-Id: I436d06bc8189fcea97e0c0842e68872afe05e022
---
graphics/java/android/graphics/BaseCanvas.java | 11 +++-
.../java/android/graphics/BaseRecordingCanvas.java | 5 +-
graphics/java/android/graphics/Mesh.java | 77 +++++++++++++++-------
.../java/android/graphics/MeshSpecification.java | 55 ++++++++++------
libs/hwui/jni/Mesh.cpp | 57 +++++++++-------
libs/hwui/jni/Mesh.h | 1 +
libs/hwui/jni/MeshSpecification.cpp | 1 -
.../src/com/android/test/hwui/MeshActivity.java | 18 ++---
8 files changed, 146 insertions(+), 79 deletions(-)
(limited to 'graphics/java')
diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java
index e62ac466ac43..2f396c026706 100644
--- a/graphics/java/android/graphics/BaseCanvas.java
+++ b/graphics/java/android/graphics/BaseCanvas.java
@@ -668,12 +668,21 @@ public abstract class BaseCanvas {
}
/**
+ * Draws a mesh object to the screen.
+ *
+ * @param mesh {@link Mesh} object that will be drawn to the screen
+ * @param blendMode {@link BlendMode} used to blend mesh primitives with the Paint color/shader
+ * @param paint {@link Paint} used to provide a color/shader/blend mode.
+ *
* @hide
*/
- public void drawMesh(Mesh mesh, BlendMode blendMode, Paint paint) {
+ public void drawMesh(@NonNull Mesh mesh, BlendMode blendMode, @NonNull Paint paint) {
if (!isHardwareAccelerated() && onHwFeatureInSwMode()) {
throw new RuntimeException("software rendering doesn't support meshes");
}
+ if (blendMode == null) {
+ blendMode = BlendMode.MODULATE;
+ }
nDrawMesh(this.mNativeCanvasWrapper, mesh.getNativeWrapperInstance(),
blendMode.getXfermode().porterDuffMode, paint.getNativeInstance());
}
diff --git a/graphics/java/android/graphics/BaseRecordingCanvas.java b/graphics/java/android/graphics/BaseRecordingCanvas.java
index eeff694eb3ac..2ec4524e1241 100644
--- a/graphics/java/android/graphics/BaseRecordingCanvas.java
+++ b/graphics/java/android/graphics/BaseRecordingCanvas.java
@@ -607,7 +607,10 @@ public class BaseRecordingCanvas extends Canvas {
}
@Override
- public final void drawMesh(Mesh mesh, BlendMode blendMode, Paint paint) {
+ public final void drawMesh(@NonNull Mesh mesh, BlendMode blendMode, @NonNull Paint paint) {
+ if (blendMode == null) {
+ blendMode = BlendMode.MODULATE;
+ }
nDrawMesh(mNativeCanvasWrapper, mesh.getNativeWrapperInstance(),
blendMode.getXfermode().porterDuffMode, paint.getNativeInstance());
}
diff --git a/graphics/java/android/graphics/Mesh.java b/graphics/java/android/graphics/Mesh.java
index 1f693166ecf0..e186386a21be 100644
--- a/graphics/java/android/graphics/Mesh.java
+++ b/graphics/java/android/graphics/Mesh.java
@@ -16,6 +16,9 @@
package android.graphics;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+
import libcore.util.NativeAllocationRegistry;
import java.nio.Buffer;
@@ -25,8 +28,8 @@ import java.nio.ShortBuffer;
* Class representing a mesh object.
*
* This class generates Mesh objects via the
- * {@link #make(MeshSpecification, Mode, Buffer, int, Rect)} and
- * {@link #makeIndexed(MeshSpecification, Mode, Buffer, int, ShortBuffer, Rect)} methods,
+ * {@link #make(MeshSpecification, int, Buffer, int, Rect)} and
+ * {@link #makeIndexed(MeshSpecification, int, Buffer, int, ShortBuffer, Rect)} methods,
* where a {@link MeshSpecification} is required along with various attributes for
* detailing the mesh object, including a mode, vertex buffer, optional index buffer, and bounds
* for the mesh. Once generated, a mesh object can be drawn through
@@ -39,9 +42,20 @@ public class Mesh {
private boolean mIsIndexed;
/**
- * Enum to determine how the mesh is represented.
+ * Determines how the mesh is represented and will be drawn.
+ */
+ @IntDef({TRIANGLES, TRIANGLE_STRIP})
+ private @interface Mode {}
+
+ /**
+ * The mesh will be drawn with triangles without utilizing shared vertices.
+ */
+ public static final int TRIANGLES = 0;
+
+ /**
+ * The mesh will be drawn with triangles utilizing shared vertices.
*/
- public enum Mode {Triangles, TriangleStrip}
+ public static final int TRIANGLE_STRIP = 1;
private static class MeshHolder {
public static final NativeAllocationRegistry MESH_SPECIFICATION_REGISTRY =
@@ -53,7 +67,8 @@ public class Mesh {
* Generates a {@link Mesh} object.
*
* @param meshSpec {@link MeshSpecification} used when generating the mesh.
- * @param mode {@link Mode} enum
+ * @param mode Determines what mode to draw the mesh in. Must be one of
+ * {@link Mesh#TRIANGLES} or {@link Mesh#TRIANGLE_STRIP}
* @param vertexBuffer vertex buffer representing through {@link Buffer}. This provides the data
* for all attributes provided within the meshSpec for every vertex. That
* is, a vertex buffer should be (attributes size * number of vertices) in
@@ -63,9 +78,13 @@ public class Mesh {
* @param bounds bounds of the mesh object.
* @return a new Mesh object.
*/
- public static Mesh make(MeshSpecification meshSpec, Mode mode, Buffer vertexBuffer,
- int vertexCount, Rect bounds) {
- long nativeMesh = nativeMake(meshSpec.mNativeMeshSpec, mode.ordinal(), vertexBuffer,
+ @NonNull
+ public static Mesh make(@NonNull MeshSpecification meshSpec, @Mode int mode,
+ @NonNull Buffer vertexBuffer, int vertexCount, @NonNull Rect bounds) {
+ if (mode != TRIANGLES && mode != TRIANGLE_STRIP) {
+ throw new IllegalArgumentException("Invalid value passed in for mode parameter");
+ }
+ long nativeMesh = nativeMake(meshSpec.mNativeMeshSpec, mode, vertexBuffer,
vertexBuffer.isDirect(), vertexCount, vertexBuffer.position(), bounds.left,
bounds.top, bounds.right, bounds.bottom);
if (nativeMesh == 0) {
@@ -78,7 +97,8 @@ public class Mesh {
* Generates a {@link Mesh} object.
*
* @param meshSpec {@link MeshSpecification} used when generating the mesh.
- * @param mode {@link Mode} enum
+ * @param mode Determines what mode to draw the mesh in. Must be one of
+ * {@link Mesh#TRIANGLES} or {@link Mesh#TRIANGLE_STRIP}
* @param vertexBuffer vertex buffer representing through {@link Buffer}. This provides the data
* for all attributes provided within the meshSpec for every vertex. That
* is, a vertex buffer should be (attributes size * number of vertices) in
@@ -92,9 +112,14 @@ public class Mesh {
* @param bounds bounds of the mesh object.
* @return a new Mesh object.
*/
- public static Mesh makeIndexed(MeshSpecification meshSpec, Mode mode, Buffer vertexBuffer,
- int vertexCount, ShortBuffer indexBuffer, Rect bounds) {
- long nativeMesh = nativeMakeIndexed(meshSpec.mNativeMeshSpec, mode.ordinal(), vertexBuffer,
+ @NonNull
+ public static Mesh makeIndexed(@NonNull MeshSpecification meshSpec, @Mode int mode,
+ @NonNull Buffer vertexBuffer, int vertexCount, @NonNull ShortBuffer indexBuffer,
+ @NonNull Rect bounds) {
+ if (mode != TRIANGLES && mode != TRIANGLE_STRIP) {
+ throw new IllegalArgumentException("Invalid value passed in for mode parameter");
+ }
+ long nativeMesh = nativeMakeIndexed(meshSpec.mNativeMeshSpec, mode, vertexBuffer,
vertexBuffer.isDirect(), vertexCount, vertexBuffer.position(), indexBuffer,
indexBuffer.isDirect(), indexBuffer.capacity(), indexBuffer.position(), bounds.left,
bounds.top, bounds.right, bounds.bottom);
@@ -114,7 +139,7 @@ public class Mesh {
* @param color the provided sRGB color will be converted into the shader program's output
* colorspace and be available as a vec4 uniform in the program.
*/
- public void setColorUniform(String uniformName, int color) {
+ public void setColorUniform(@NonNull String uniformName, int color) {
setUniform(uniformName, Color.valueOf(color).getComponents(), true);
}
@@ -128,7 +153,7 @@ public class Mesh {
* @param color the provided sRGB color will be converted into the shader program's output
* colorspace and be available as a vec4 uniform in the program.
*/
- public void setColorUniform(String uniformName, long color) {
+ public void setColorUniform(@NonNull String uniformName, long color) {
Color exSRGB = Color.valueOf(color).convert(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB));
setUniform(uniformName, exSRGB.getComponents(), true);
}
@@ -143,7 +168,7 @@ public class Mesh {
* @param color the provided sRGB color will be converted into the shader program's output
* colorspace and will be made available as a vec4 uniform in the program.
*/
- public void setColorUniform(String uniformName, Color color) {
+ public void setColorUniform(@NonNull String uniformName, @NonNull Color color) {
if (color == null) {
throw new NullPointerException("The color parameter must not be null");
}
@@ -160,7 +185,7 @@ public class Mesh {
* @param uniformName name matching the float uniform declared in the shader program.
* @param value float value corresponding to the float uniform with the given name.
*/
- public void setFloatUniform(String uniformName, float value) {
+ public void setFloatUniform(@NonNull String uniformName, float value) {
setFloatUniform(uniformName, value, 0.0f, 0.0f, 0.0f, 1);
}
@@ -173,7 +198,7 @@ public class Mesh {
* @param value1 first float value corresponding to the float uniform with the given name.
* @param value2 second float value corresponding to the float uniform with the given name.
*/
- public void setFloatUniform(String uniformName, float value1, float value2) {
+ public void setFloatUniform(@NonNull String uniformName, float value1, float value2) {
setFloatUniform(uniformName, value1, value2, 0.0f, 0.0f, 2);
}
@@ -188,7 +213,8 @@ public class Mesh {
* @param value3 third float value corresponding to the float unifiform with the given
* name.
*/
- public void setFloatUniform(String uniformName, float value1, float value2, float value3) {
+ public void setFloatUniform(
+ @NonNull String uniformName, float value1, float value2, float value3) {
setFloatUniform(uniformName, value1, value2, value3, 0.0f, 3);
}
@@ -204,7 +230,7 @@ public class Mesh {
* @param value4 fourth float value corresponding to the float uniform with the given name.
*/
public void setFloatUniform(
- String uniformName, float value1, float value2, float value3, float value4) {
+ @NonNull String uniformName, float value1, float value2, float value3, float value4) {
setFloatUniform(uniformName, value1, value2, value3, value4, 4);
}
@@ -217,7 +243,7 @@ public class Mesh {
* @param uniformName name matching the float uniform declared in the shader program.
* @param values float value corresponding to the vec4 float uniform with the given name.
*/
- public void setFloatUniform(String uniformName, float[] values) {
+ public void setFloatUniform(@NonNull String uniformName, @NonNull float[] values) {
setUniform(uniformName, values, false);
}
@@ -249,7 +275,7 @@ public class Mesh {
* @param uniformName name matching the int uniform delcared in the shader program.
* @param value value corresponding to the int uniform with the given name.
*/
- public void setIntUniform(String uniformName, int value) {
+ public void setIntUniform(@NonNull String uniformName, int value) {
setIntUniform(uniformName, value, 0, 0, 0, 1);
}
@@ -262,7 +288,7 @@ public class Mesh {
* @param value1 first value corresponding to the int uniform with the given name.
* @param value2 second value corresponding to the int uniform with the given name.
*/
- public void setIntUniform(String uniformName, int value1, int value2) {
+ public void setIntUniform(@NonNull String uniformName, int value1, int value2) {
setIntUniform(uniformName, value1, value2, 0, 0, 2);
}
@@ -276,7 +302,7 @@ public class Mesh {
* @param value2 second value corresponding to the int uniform with the given name.
* @param value3 third value corresponding to the int uniform with the given name.
*/
- public void setIntUniform(String uniformName, int value1, int value2, int value3) {
+ public void setIntUniform(@NonNull String uniformName, int value1, int value2, int value3) {
setIntUniform(uniformName, value1, value2, value3, 0, 3);
}
@@ -291,7 +317,8 @@ public class Mesh {
* @param value3 third value corresponding to the int uniform with the given name.
* @param value4 fourth value corresponding to the int uniform with the given name.
*/
- public void setIntUniform(String uniformName, int value1, int value2, int value3, int value4) {
+ public void setIntUniform(
+ @NonNull String uniformName, int value1, int value2, int value3, int value4) {
setIntUniform(uniformName, value1, value2, value3, value4, 4);
}
@@ -304,7 +331,7 @@ public class Mesh {
* @param uniformName name matching the int uniform delcared in the shader program.
* @param values int values corresponding to the vec4 int uniform with the given name.
*/
- public void setIntUniform(String uniformName, int[] values) {
+ public void setIntUniform(@NonNull String uniformName, @NonNull int[] values) {
if (uniformName == null) {
throw new NullPointerException("The uniformName parameter must not be null");
}
diff --git a/graphics/java/android/graphics/MeshSpecification.java b/graphics/java/android/graphics/MeshSpecification.java
index dd8fb7ab2370..6ef35960a73b 100644
--- a/graphics/java/android/graphics/MeshSpecification.java
+++ b/graphics/java/android/graphics/MeshSpecification.java
@@ -17,15 +17,18 @@
package android.graphics;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import libcore.util.NativeAllocationRegistry;
+import java.util.List;
+
/**
* Class responsible for holding specifications for {@link Mesh} creations. This class
* generates a {@link MeshSpecification} via the Make method, where multiple parameters to set up
* the mesh are supplied, including attributes, vertex stride, varyings, and
* vertex/fragment shaders. There are also additional methods to provide an optional
- * {@link ColorSpace} as well as an {@link AlphaType}.
+ * {@link ColorSpace} as well as an alpha type.
*
* Note that there are several limitations on various mesh specifications:
* 1. The max amount of attributes allowed is 8.
@@ -42,12 +45,11 @@ public class MeshSpecification {
long mNativeMeshSpec;
/**
- * Constants for {@link #make(Attribute[], int, Varying[], String, String, ColorSpace, int)}
+ * Constants for {@link #make(List, int, List, String, String)}
* to determine alpha type. Describes how to interpret the alpha component of a pixel.
*/
@IntDef({UNKNOWN, OPAQUE, PREMUL, UNPREMULT})
- public @interface AlphaType {
- }
+ private @interface AlphaType {}
/**
* uninitialized.
@@ -73,8 +75,7 @@ public class MeshSpecification {
* Constants for {@link Attribute} and {@link Varying} for determining the data type.
*/
@IntDef({FLOAT, FLOAT2, FLOAT3, FLOAT4, UBYTE4})
- public @interface Type {
- }
+ private @interface Type {}
/**
* Represents one float. Its equivalent shader type is float.
@@ -118,7 +119,7 @@ public class MeshSpecification {
private int mOffset;
private String mName;
- public Attribute(@Type int type, int offset, String name) {
+ public Attribute(@Type int type, int offset, @NonNull String name) {
mType = type;
mOffset = offset;
mName = name;
@@ -134,7 +135,7 @@ public class MeshSpecification {
private int mType;
private String mName;
- public Varying(@Type int type, String name) {
+ public Varying(@Type int type, @NonNull String name) {
mType = type;
mName = name;
}
@@ -162,10 +163,13 @@ public class MeshSpecification {
* @param fragmentShader fragment shader to be supplied to the mesh.
* @return {@link MeshSpecification} object for use when creating {@link Mesh}
*/
- public static MeshSpecification make(Attribute[] attributes, int vertexStride,
- Varying[] varyings, String vertexShader, String fragmentShader) {
- long nativeMeshSpec =
- nativeMake(attributes, vertexStride, varyings, vertexShader, fragmentShader);
+ @NonNull
+ public static MeshSpecification make(@NonNull List attributes, int vertexStride,
+ @NonNull List varyings, @NonNull String vertexShader,
+ @NonNull String fragmentShader) {
+ long nativeMeshSpec = nativeMake(attributes.toArray(new Attribute[attributes.size()]),
+ vertexStride, varyings.toArray(new Varying[varyings.size()]), vertexShader,
+ fragmentShader);
if (nativeMeshSpec == 0) {
throw new IllegalArgumentException("MeshSpecification construction failed");
}
@@ -189,9 +193,12 @@ public class MeshSpecification {
* @param colorSpace {@link ColorSpace} to tell what color space to work in.
* @return {@link MeshSpecification} object for use when creating {@link Mesh}
*/
- public static MeshSpecification make(Attribute[] attributes, int vertexStride,
- Varying[] varyings, String vertexShader, String fragmentShader, ColorSpace colorSpace) {
- long nativeMeshSpec = nativeMakeWithCS(attributes, vertexStride, varyings, vertexShader,
+ @NonNull
+ public static MeshSpecification make(@NonNull List attributes, int vertexStride,
+ @NonNull List varyings, @NonNull String vertexShader,
+ @NonNull String fragmentShader, @NonNull ColorSpace colorSpace) {
+ long nativeMeshSpec = nativeMakeWithCS(attributes.toArray(new Attribute[attributes.size()]),
+ vertexStride, varyings.toArray(new Varying[varyings.size()]), vertexShader,
fragmentShader, colorSpace.getNativeInstance());
if (nativeMeshSpec == 0) {
throw new IllegalArgumentException("MeshSpecification construction failed");
@@ -215,14 +222,22 @@ public class MeshSpecification {
* @param fragmentShader fragment shader to be supplied to the mesh.
* @param colorSpace {@link ColorSpace} to tell what color space to work in.
* @param alphaType Describes how to interpret the alpha component for a pixel. Must be
- * one of {@link AlphaType} values.
+ * one of
+ * {@link MeshSpecification#UNKNOWN},
+ * {@link MeshSpecification#OPAQUE},
+ * {@link MeshSpecification#PREMUL}, or
+ * {@link MeshSpecification#UNPREMULT}
* @return {@link MeshSpecification} object for use when creating {@link Mesh}
*/
- public static MeshSpecification make(Attribute[] attributes, int vertexStride,
- Varying[] varyings, String vertexShader, String fragmentShader, ColorSpace colorSpace,
+ @NonNull
+ public static MeshSpecification make(@NonNull List attributes, int vertexStride,
+ @NonNull List varyings, @NonNull String vertexShader,
+ @NonNull String fragmentShader, @NonNull ColorSpace colorSpace,
@AlphaType int alphaType) {
- long nativeMeshSpec = nativeMakeWithAlpha(attributes, vertexStride, varyings, vertexShader,
- fragmentShader, colorSpace.getNativeInstance(), alphaType);
+ long nativeMeshSpec =
+ nativeMakeWithAlpha(attributes.toArray(new Attribute[attributes.size()]),
+ vertexStride, varyings.toArray(new Varying[varyings.size()]), vertexShader,
+ fragmentShader, colorSpace.getNativeInstance(), alphaType);
if (nativeMeshSpec == 0) {
throw new IllegalArgumentException("MeshSpecification construction failed");
}
diff --git a/libs/hwui/jni/Mesh.cpp b/libs/hwui/jni/Mesh.cpp
index 7b9a93fe0fba..3aac48dd08b1 100644
--- a/libs/hwui/jni/Mesh.cpp
+++ b/libs/hwui/jni/Mesh.cpp
@@ -44,10 +44,16 @@ static jlong make(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject verte
sk_sp skVertexBuffer =
genVertexBuffer(env, vertexBuffer, vertexCount * skMeshSpec->stride(), isDirect);
auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
- auto mesh = SkMesh::Make(skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount,
- vertexOffset, nullptr, skRect)
- .mesh;
- auto meshPtr = std::make_unique(MeshWrapper{mesh, MeshUniformBuilder(skMeshSpec)});
+ auto meshResult = SkMesh::Make(
+ skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount, vertexOffset,
+ SkData::MakeWithCopy(skMeshSpec->uniforms().data(), skMeshSpec->uniformSize()), skRect);
+
+ if (!meshResult.error.isEmpty()) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", meshResult.error.c_str());
+ }
+
+ auto meshPtr = std::make_unique(
+ MeshWrapper{meshResult.mesh, MeshUniformBuilder(skMeshSpec)});
return reinterpret_cast(meshPtr.release());
}
@@ -61,11 +67,17 @@ static jlong makeIndexed(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobjec
sk_sp skIndexBuffer =
genIndexBuffer(env, indexBuffer, indexCount * gIndexByteSize, isIndexDirect);
auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
- auto mesh = SkMesh::MakeIndexed(skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount,
- vertexOffset, skIndexBuffer, indexCount, indexOffset, nullptr,
- skRect)
- .mesh;
- auto meshPtr = std::make_unique(MeshWrapper{mesh, MeshUniformBuilder(skMeshSpec)});
+
+ auto meshResult = SkMesh::MakeIndexed(
+ skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount, vertexOffset,
+ skIndexBuffer, indexCount, indexOffset,
+ SkData::MakeWithCopy(skMeshSpec->uniforms().data(), skMeshSpec->uniformSize()), skRect);
+
+ if (!meshResult.error.isEmpty()) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", meshResult.error.c_str());
+ }
+ auto meshPtr = std::make_unique(
+ MeshWrapper{meshResult.mesh, MeshUniformBuilder(skMeshSpec)});
return reinterpret_cast(meshPtr.release());
}
@@ -139,22 +151,22 @@ static void nativeUpdateFloatUniforms(JNIEnv* env, MeshUniformBuilder* builder,
}
}
-static void updateFloatUniforms(JNIEnv* env, jobject, jlong uniBuilder, jstring uniformName,
+static void updateFloatUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
jfloat value1, jfloat value2, jfloat value3, jfloat value4,
jint count) {
- auto* builder = reinterpret_cast(uniBuilder);
+ auto* wrapper = reinterpret_cast(meshWrapper);
ScopedUtfChars name(env, uniformName);
const float values[4] = {value1, value2, value3, value4};
- nativeUpdateFloatUniforms(env, builder, name.c_str(), values, count, false);
+ nativeUpdateFloatUniforms(env, &wrapper->builder, name.c_str(), values, count, false);
}
-static void updateFloatArrayUniforms(JNIEnv* env, jobject, jlong uniBuilder, jstring jUniformName,
+static void updateFloatArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring jUniformName,
jfloatArray jvalues, jboolean isColor) {
- auto builder = reinterpret_cast(uniBuilder);
+ auto wrapper = reinterpret_cast(meshWrapper);
ScopedUtfChars name(env, jUniformName);
AutoJavaFloatArray autoValues(env, jvalues, 0, kRO_JNIAccess);
- nativeUpdateFloatUniforms(env, builder, name.c_str(), autoValues.ptr(), autoValues.length(),
- isColor);
+ nativeUpdateFloatUniforms(env, &wrapper->builder, name.c_str(), autoValues.ptr(),
+ autoValues.length(), isColor);
}
static void nativeUpdateIntUniforms(JNIEnv* env, MeshUniformBuilder* builder,
@@ -171,20 +183,21 @@ static void nativeUpdateIntUniforms(JNIEnv* env, MeshUniformBuilder* builder,
}
}
-static void updateIntUniforms(JNIEnv* env, jobject, jlong uniBuilder, jstring uniformName,
+static void updateIntUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
jint value1, jint value2, jint value3, jint value4, jint count) {
- auto builder = reinterpret_cast(uniBuilder);
+ auto wrapper = reinterpret_cast(meshWrapper);
ScopedUtfChars name(env, uniformName);
const int values[4] = {value1, value2, value3, value4};
- nativeUpdateIntUniforms(env, builder, name.c_str(), values, count);
+ nativeUpdateIntUniforms(env, &wrapper->builder, name.c_str(), values, count);
}
-static void updateIntArrayUniforms(JNIEnv* env, jobject, jlong uniBuilder, jstring uniformName,
+static void updateIntArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
jintArray values) {
- auto builder = reinterpret_cast(uniBuilder);
+ auto wrapper = reinterpret_cast(meshWrapper);
ScopedUtfChars name(env, uniformName);
AutoJavaIntArray autoValues(env, values, 0);
- nativeUpdateIntUniforms(env, builder, name.c_str(), autoValues.ptr(), autoValues.length());
+ nativeUpdateIntUniforms(env, &wrapper->builder, name.c_str(), autoValues.ptr(),
+ autoValues.length());
}
static void MeshWrapper_destroy(MeshWrapper* wrapper) {
diff --git a/libs/hwui/jni/Mesh.h b/libs/hwui/jni/Mesh.h
index aa014a5e4f61..7a73f2d5c470 100644
--- a/libs/hwui/jni/Mesh.h
+++ b/libs/hwui/jni/Mesh.h
@@ -239,6 +239,7 @@ public:
explicit MeshUniformBuilder(sk_sp meshSpec) {
fMeshSpec = sk_sp(meshSpec);
+ fUniforms = (SkData::MakeZeroInitialized(meshSpec->uniformSize()));
}
sk_sp fUniforms;
diff --git a/libs/hwui/jni/MeshSpecification.cpp b/libs/hwui/jni/MeshSpecification.cpp
index 619a3ed552d8..ae9792df3d82 100644
--- a/libs/hwui/jni/MeshSpecification.cpp
+++ b/libs/hwui/jni/MeshSpecification.cpp
@@ -78,7 +78,6 @@ static jlong Make(JNIEnv* env, jobject thiz, jobjectArray attributeArray, jint v
auto meshSpecResult = SkMeshSpecification::Make(attributes, vertexStride, varyings,
SkString(skVertexShader.c_str()),
SkString(skFragmentShader.c_str()));
-
if (meshSpecResult.specification.get() == nullptr) {
jniThrowException(env, "java/lang/IllegalArgumentException", meshSpecResult.error.c_str());
}
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java
index efe242ce728e..d3a5885b324d 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java
@@ -32,6 +32,7 @@ import android.view.View;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
+import java.util.ArrayList;
public class MeshActivity extends Activity {
@Override
@@ -64,7 +65,9 @@ public class MeshActivity extends Activity {
vertexBuffer.put(5, 400.0f);
vertexBuffer.rewind();
Mesh mesh = Mesh.make(
- meshSpec, Mesh.Mode.Triangles, vertexBuffer, 3, new Rect(0, 0, 1000, 1000));
+ meshSpec, Mesh.TRIANGLES, vertexBuffer, 3, new Rect(0, 0, 1000, 1000));
+
+ canvas.drawMesh(mesh, BlendMode.COLOR, new Paint());
int numTriangles = 100;
// number of triangles plus first 2 vertices
@@ -95,12 +98,10 @@ public class MeshActivity extends Activity {
}
iVertexBuffer.rewind();
indexBuffer.rewind();
- Mesh mesh2 = Mesh.makeIndexed(meshSpec, Mesh.Mode.Triangles, iVertexBuffer, 102,
- indexBuffer, new Rect(0, 0, 1000, 1000));
-
+ Mesh mesh2 = Mesh.makeIndexed(meshSpec, Mesh.TRIANGLES, iVertexBuffer, 102, indexBuffer,
+ new Rect(0, 0, 1000, 1000));
Paint paint = new Paint();
paint.setColor(Color.RED);
- canvas.drawMesh(mesh, BlendMode.COLOR, new Paint());
canvas.drawMesh(mesh2, BlendMode.COLOR, paint);
}
@@ -114,10 +115,9 @@ public class MeshActivity extends Activity {
+ " color = vec4(1.0, 0.0, 0.0, 1.0);"
+ " return varyings.position;\n"
+ "}";
- Attribute[] attList =
- new Attribute[] {new Attribute(MeshSpecification.FLOAT2, 0, "position")};
- Varying[] varyList =
- new MeshSpecification.Varying[] {};
+ ArrayList attList = new ArrayList<>();
+ attList.add(new Attribute(MeshSpecification.FLOAT2, 0, "position"));
+ ArrayList varyList = new ArrayList<>();
return MeshSpecification.make(attList, 8, varyList, vs, fs);
}
}
--
cgit v1.2.3-59-g8ed1b
From 5d3141ffeccd01061c5e74f95c77aca6ccd36dc7 Mon Sep 17 00:00:00 2001
From: Angel Aguayo
Date: Tue, 20 Dec 2022 20:44:34 +0000
Subject: Publicly Expose drawMesh APIs
Bug: b/253321460
Test: None
Change-Id: I65486ba995577391a2886fe0d5f472c2a34480da
---
core/api/current.txt | 45 ++++++++++++++++++++++
graphics/java/android/graphics/BaseCanvas.java | 2 -
graphics/java/android/graphics/Mesh.java | 2 -
.../java/android/graphics/MeshSpecification.java | 2 -
4 files changed, 45 insertions(+), 6 deletions(-)
(limited to 'graphics/java')
diff --git a/core/api/current.txt b/core/api/current.txt
index 74b3ba93564a..9c4cc2e20233 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -14631,6 +14631,7 @@ package android.graphics {
method public void drawLine(float, float, float, float, @NonNull android.graphics.Paint);
method public void drawLines(@NonNull @Size(multiple=4) float[], int, int, @NonNull android.graphics.Paint);
method public void drawLines(@NonNull @Size(multiple=4) float[], @NonNull android.graphics.Paint);
+ method public void drawMesh(@NonNull android.graphics.Mesh, android.graphics.BlendMode, @NonNull android.graphics.Paint);
method public void drawOval(@NonNull android.graphics.RectF, @NonNull android.graphics.Paint);
method public void drawOval(float, float, float, float, @NonNull android.graphics.Paint);
method public void drawPaint(@NonNull android.graphics.Paint);
@@ -15206,6 +15207,49 @@ package android.graphics {
enum_constant public static final android.graphics.Matrix.ScaleToFit START;
}
+ public class Mesh {
+ method @NonNull public static android.graphics.Mesh make(@NonNull android.graphics.MeshSpecification, int, @NonNull java.nio.Buffer, int, @NonNull android.graphics.Rect);
+ method @NonNull public static android.graphics.Mesh makeIndexed(@NonNull android.graphics.MeshSpecification, int, @NonNull java.nio.Buffer, int, @NonNull java.nio.ShortBuffer, @NonNull android.graphics.Rect);
+ method public void setColorUniform(@NonNull String, int);
+ method public void setColorUniform(@NonNull String, long);
+ method public void setColorUniform(@NonNull String, @NonNull android.graphics.Color);
+ method public void setFloatUniform(@NonNull String, float);
+ method public void setFloatUniform(@NonNull String, float, float);
+ method public void setFloatUniform(@NonNull String, float, float, float);
+ method public void setFloatUniform(@NonNull String, float, float, float, float);
+ method public void setFloatUniform(@NonNull String, @NonNull float[]);
+ method public void setIntUniform(@NonNull String, int);
+ method public void setIntUniform(@NonNull String, int, int);
+ method public void setIntUniform(@NonNull String, int, int, int);
+ method public void setIntUniform(@NonNull String, int, int, int, int);
+ method public void setIntUniform(@NonNull String, @NonNull int[]);
+ field public static final int TRIANGLES = 0; // 0x0
+ field public static final int TRIANGLE_STRIP = 1; // 0x1
+ }
+
+ public class MeshSpecification {
+ method @NonNull public static android.graphics.MeshSpecification make(@NonNull java.util.List, int, @NonNull java.util.List, @NonNull String, @NonNull String);
+ method @NonNull public static android.graphics.MeshSpecification make(@NonNull java.util.List, int, @NonNull java.util.List, @NonNull String, @NonNull String, @NonNull android.graphics.ColorSpace);
+ method @NonNull public static android.graphics.MeshSpecification make(@NonNull java.util.List, int, @NonNull java.util.List, @NonNull String, @NonNull String, @NonNull android.graphics.ColorSpace, int);
+ field public static final int FLOAT = 0; // 0x0
+ field public static final int FLOAT2 = 1; // 0x1
+ field public static final int FLOAT3 = 2; // 0x2
+ field public static final int FLOAT4 = 3; // 0x3
+ field public static final int OPAQUE = 1; // 0x1
+ field public static final int PREMUL = 2; // 0x2
+ field public static final int UBYTE4 = 4; // 0x4
+ field public static final int UNKNOWN = 0; // 0x0
+ field public static final int UNPREMULT = 3; // 0x3
+ }
+
+ public static class MeshSpecification.Attribute {
+ ctor public MeshSpecification.Attribute(int, int, @NonNull String);
+ }
+
+ public static class MeshSpecification.Varying {
+ ctor public MeshSpecification.Varying(int, @NonNull String);
+ }
+
@Deprecated public class Movie {
method @Deprecated public static android.graphics.Movie decodeByteArray(byte[], int, int);
method @Deprecated public static android.graphics.Movie decodeFile(String);
@@ -15721,6 +15765,7 @@ package android.graphics {
}
public final class RecordingCanvas extends android.graphics.Canvas {
+ method public final void drawMesh(@NonNull android.graphics.Mesh, android.graphics.BlendMode, @NonNull android.graphics.Paint);
}
public final class Rect implements android.os.Parcelable {
diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java
index 2f396c026706..00ffd09eb29e 100644
--- a/graphics/java/android/graphics/BaseCanvas.java
+++ b/graphics/java/android/graphics/BaseCanvas.java
@@ -673,8 +673,6 @@ public abstract class BaseCanvas {
* @param mesh {@link Mesh} object that will be drawn to the screen
* @param blendMode {@link BlendMode} used to blend mesh primitives with the Paint color/shader
* @param paint {@link Paint} used to provide a color/shader/blend mode.
- *
- * @hide
*/
public void drawMesh(@NonNull Mesh mesh, BlendMode blendMode, @NonNull Paint paint) {
if (!isHardwareAccelerated() && onHwFeatureInSwMode()) {
diff --git a/graphics/java/android/graphics/Mesh.java b/graphics/java/android/graphics/Mesh.java
index e186386a21be..a599b2c17604 100644
--- a/graphics/java/android/graphics/Mesh.java
+++ b/graphics/java/android/graphics/Mesh.java
@@ -34,8 +34,6 @@ import java.nio.ShortBuffer;
* detailing the mesh object, including a mode, vertex buffer, optional index buffer, and bounds
* for the mesh. Once generated, a mesh object can be drawn through
* {@link Canvas#drawMesh(Mesh, BlendMode, Paint)}
- *
- * @hide
*/
public class Mesh {
private long mNativeMeshWrapper;
diff --git a/graphics/java/android/graphics/MeshSpecification.java b/graphics/java/android/graphics/MeshSpecification.java
index 6ef35960a73b..52b40029a0eb 100644
--- a/graphics/java/android/graphics/MeshSpecification.java
+++ b/graphics/java/android/graphics/MeshSpecification.java
@@ -38,8 +38,6 @@ import java.util.List;
*
* These should be kept in mind when generating a mesh specification, as exceeding them will
* lead to errors.
- *
- * @hide
*/
public class MeshSpecification {
long mNativeMeshSpec;
--
cgit v1.2.3-59-g8ed1b
From 9cda0d98c19c46e52c104c470aa0a0c4ad4b6429 Mon Sep 17 00:00:00 2001
From: Sally Qi
Date: Wed, 21 Dec 2022 12:32:29 -0800
Subject: Update javadoc for new added ColorSpace.
- The color space with non ICC transfer function is allowed.
Bug: 241284309
Test: builds
Change-Id: I64e4b887599ed60d79ed48e9d3c1f6748f12930f
---
graphics/java/android/graphics/Bitmap.java | 13 +++++++++----
graphics/java/android/graphics/ImageDecoder.java | 9 +++++++--
2 files changed, 16 insertions(+), 6 deletions(-)
(limited to 'graphics/java')
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 318cd32d19fe..3c654d6dec48 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -1810,10 +1810,15 @@ public final class Bitmap implements Parcelable {
* {@code ColorSpace}.
*
* @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.
+ * {@link ColorSpace.Model#RGB RGB}, or whose components min/max values reduce
+ * the numerical range compared to the previously assigned color space.
+ * Prior to {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * IllegalArgumentException will also be thrown
+ * if the specified color space has a transfer function that is not an
+ * {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}. Starting from
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, the color spaces with non
+ * ICC parametric curve transfer function are allowed.
+ * E.g., {@link ColorSpace.Named#BT2020_HLG BT2020_HLG}.
*
* @throws IllegalArgumentException If the {@code Config} (returned by {@link #getConfig()})
* is {@link Config#ALPHA_8}.
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index 239621eeed1e..51f99ec637da 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -1682,11 +1682,16 @@ public final class ImageDecoder implements AutoCloseable {
* {@link #decodeBitmap decodeBitmap} when setting a non-RGB color space
* such as {@link ColorSpace.Named#CIE_LAB Lab}.
*
- * The specified color space's transfer function must be
+ *
Prior to {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * the specified color space's transfer function must be
* an {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}. An
* IllegalArgumentException will be thrown by the decode methods
* if calling {@link ColorSpace.Rgb#getTransferParameters()} on the
- * specified color space returns null.
+ * specified color space returns null.
+ * Starting from {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * the color spaces with non ICC parametric curve transfer function are allowed.
+ * E.g., {@link ColorSpace.Named#BT2020_HLG BT2020_HLG}.
+ *
*
* Like all setters on ImageDecoder, this must be called inside
* {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded}.
--
cgit v1.2.3-59-g8ed1b
From ac5f755472e02f039f947ccfd5f5282e0ac80fe3 Mon Sep 17 00:00:00 2001
From: Matt Buckley
Date: Mon, 19 Dec 2022 22:03:27 +0000
Subject: Send load up hint on view inflation
In order for HWUI to keep up with sudden changes in workload, it
needs to send hints in advance of view inflation in order to compensate
for these increases. This patch allows HWUI to inform PowerHAL about
upcoming spikes in workload.
Bug: b/261130508
Test: manual
Change-Id: Ie0bb80d021dee13067d1960276065f6f2e0af34d
---
core/java/android/view/LayoutInflater.java | 8 ++++++++
core/java/android/view/ThreadedRenderer.java | 7 +++++++
core/java/android/view/ViewRootImpl.java | 12 ++++++++++++
graphics/java/android/graphics/HardwareRenderer.java | 11 +++++++++++
libs/hwui/jni/android_graphics_HardwareRenderer.cpp | 7 +++++++
libs/hwui/renderthread/CanvasContext.cpp | 4 ++++
libs/hwui/renderthread/CanvasContext.h | 2 ++
libs/hwui/renderthread/HintSessionWrapper.cpp | 5 +++++
libs/hwui/renderthread/HintSessionWrapper.h | 1 +
libs/hwui/renderthread/RenderProxy.cpp | 4 ++++
libs/hwui/renderthread/RenderProxy.h | 1 +
11 files changed, 62 insertions(+)
(limited to 'graphics/java')
diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java
index df78827534a6..99a7fe598936 100644
--- a/core/java/android/view/LayoutInflater.java
+++ b/core/java/android/view/LayoutInflater.java
@@ -640,6 +640,10 @@ public abstract class LayoutInflater {
mConstructorArgs[0] = inflaterContext;
View result = root;
+ if (root != null && root.getViewRootImpl() != null) {
+ root.getViewRootImpl().notifyRendererOfExpensiveFrame();
+ }
+
try {
advanceToRootNode(parser);
final String name = parser.getName();
@@ -662,6 +666,10 @@ public abstract class LayoutInflater {
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
+ if (root == null && temp != null && temp.getViewRootImpl() != null) {
+ temp.getViewRootImpl().notifyRendererOfExpensiveFrame();
+ }
+
ViewGroup.LayoutParams params = null;
if (root != null) {
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 164a49464051..9c6e823b549e 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -594,6 +594,13 @@ public final class ThreadedRenderer extends HardwareRenderer {
}
}
+ @Override
+ public void notifyExpensiveFrame() {
+ if (isEnabled()) {
+ super.notifyExpensiveFrame();
+ }
+ }
+
/**
* Updates the light position based on the position of the window.
*
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index ea7a64ef5c24..30e98a4e64f7 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2334,6 +2334,18 @@ public final class ViewRootImpl implements ViewParent,
}
}
+ /**
+ * Notifies the HardwareRenderer of an expensive upcoming frame, to
+ * allow better handling of power and scheduling requirements.
+ *
+ * @hide
+ */
+ void notifyRendererOfExpensiveFrame() {
+ if (mAttachInfo.mThreadedRenderer != null) {
+ mAttachInfo.mThreadedRenderer.notifyExpensiveFrame();
+ }
+ }
+
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
void scheduleTraversals() {
if (!mTraversalScheduled) {
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index c7c97e0b82b9..0892a9b14738 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -991,6 +991,15 @@ public class HardwareRenderer {
nNotifyCallbackPending(mNativeProxy);
}
+ /**
+ * Notifies the hardware renderer about upcoming expensive frames.
+ *
+ * @hide
+ */
+ public void notifyExpensiveFrame() {
+ nNotifyExpensiveFrame(mNativeProxy);
+ }
+
/**
* b/68769804, b/66945974: For low FPS experiments.
*
@@ -1551,4 +1560,6 @@ public class HardwareRenderer {
private static native void nSetRtAnimationsEnabled(boolean rtAnimationsEnabled);
private static native void nNotifyCallbackPending(long nativeProxy);
+
+ private static native void nNotifyExpensiveFrame(long nativeProxy);
}
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index 0663121a4027..e457c311f689 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -865,6 +865,11 @@ static void android_view_ThreadedRenderer_notifyCallbackPending(JNIEnv*, jclass,
proxy->notifyCallbackPending();
}
+static void android_view_ThreadedRenderer_notifyExpensiveFrame(JNIEnv*, jclass, jlong proxyPtr) {
+ RenderProxy* proxy = reinterpret_cast(proxyPtr);
+ proxy->notifyExpensiveFrame();
+}
+
// Plumbs the display density down to DeviceInfo.
static void android_view_ThreadedRenderer_setDisplayDensityDpi(JNIEnv*, jclass, jint densityDpi) {
// Convert from dpi to density-independent pixels.
@@ -1044,6 +1049,8 @@ static const JNINativeMethod gMethods[] = {
(void*)android_view_ThreadedRenderer_setRtAnimationsEnabled},
{"nNotifyCallbackPending", "(J)V",
(void*)android_view_ThreadedRenderer_notifyCallbackPending},
+ {"nNotifyExpensiveFrame", "(J)V",
+ (void*)android_view_ThreadedRenderer_notifyExpensiveFrame},
};
static JavaVM* mJvm = nullptr;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 64839d0147f8..b92c90edd1ca 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -1006,6 +1006,10 @@ void CanvasContext::sendLoadResetHint() {
mHintSessionWrapper.sendLoadResetHint();
}
+void CanvasContext::sendLoadIncreaseHint() {
+ mHintSessionWrapper.sendLoadIncreaseHint();
+}
+
void CanvasContext::setSyncDelayDuration(nsecs_t duration) {
mSyncDelayDuration = duration;
}
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index e875c42e9eba..d29e12c38d9f 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -218,6 +218,8 @@ public:
void sendLoadResetHint();
+ void sendLoadIncreaseHint();
+
void setSyncDelayDuration(nsecs_t duration);
private:
diff --git a/libs/hwui/renderthread/HintSessionWrapper.cpp b/libs/hwui/renderthread/HintSessionWrapper.cpp
index 94c9d94a7c26..dece548e9dc1 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.cpp
+++ b/libs/hwui/renderthread/HintSessionWrapper.cpp
@@ -158,6 +158,11 @@ void HintSessionWrapper::sendLoadResetHint() {
mLastFrameNotification = now;
}
+void HintSessionWrapper::sendLoadIncreaseHint() {
+ if (!useHintSession()) return;
+ gAPH_sendHintFn(mHintSession, static_cast(SessionHint::CPU_LOAD_UP));
+}
+
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/renderthread/HintSessionWrapper.h b/libs/hwui/renderthread/HintSessionWrapper.h
index fcbc10185255..c0f7a57bed75 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.h
+++ b/libs/hwui/renderthread/HintSessionWrapper.h
@@ -33,6 +33,7 @@ public:
void updateTargetWorkDuration(long targetDurationNanos);
void reportActualWorkDuration(long actualDurationNanos);
void sendLoadResetHint();
+ void sendLoadIncreaseHint();
private:
bool useHintSession();
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 07f5a78fc1ec..04a6cbd1a577 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -242,6 +242,10 @@ void RenderProxy::notifyCallbackPending() {
mRenderThread.queue().post([this]() { mContext->sendLoadResetHint(); });
}
+void RenderProxy::notifyExpensiveFrame() {
+ mRenderThread.queue().post([this]() { mContext->sendLoadIncreaseHint(); });
+}
+
void RenderProxy::dumpProfileInfo(int fd, int dumpFlags) {
mRenderThread.queue().runSync([&]() {
std::lock_guard lock(mRenderThread.getJankDataMutex());
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index a21faa86ed0f..1064225b69ae 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -110,6 +110,7 @@ public:
void stopDrawing();
void notifyFramePending();
void notifyCallbackPending();
+ void notifyExpensiveFrame();
void dumpProfileInfo(int fd, int dumpFlags);
// Not exported, only used for testing
--
cgit v1.2.3-59-g8ed1b
From 4cf47f1b79d4b9ec4cc9dd19cb9a550d0bb21280 Mon Sep 17 00:00:00 2001
From: Chet Haase
Date: Wed, 4 Jan 2023 20:56:32 +0000
Subject: Change behavior for next() and hasNext() for empty paths
Original behavior treated DONE like any other path verb, and threw
an exception when querying an iterator that had already issued
VERB_DONE. Also, hasNext() returned false after when next() would
result in an exception.
New behavior treats DONE as just informational:
- An empty iterator, or one with no more verbs to iterate on, will
always return VERB_DONE for next()
- hasNext() will only return true when there are non-DONE verbs left to
return from next().
Also, clarified docs for the verbs.
Bug: 264437928
Test: PathTest and PathIteratorTest, including new test for hasNext()
Change-Id: Icc0517ac3c2a346a3271ff2f78e7629e4ebb5d0a
---
graphics/java/android/graphics/PathIterator.java | 26 +++++++++---------------
1 file changed, 10 insertions(+), 16 deletions(-)
(limited to 'graphics/java')
diff --git a/graphics/java/android/graphics/PathIterator.java b/graphics/java/android/graphics/PathIterator.java
index 33b9a475ad21..a77457dd2431 100644
--- a/graphics/java/android/graphics/PathIterator.java
+++ b/graphics/java/android/graphics/PathIterator.java
@@ -29,8 +29,6 @@ import libcore.util.NativeAllocationRegistry;
import java.lang.annotation.Retention;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
-import java.util.NoSuchElementException;
-
/**
* PathIterator can be used to query a given {@link Path} object, to discover its
@@ -54,7 +52,8 @@ public class PathIterator implements Iterator {
/**
* The Verb indicates the operation for a given segment of a path. These
* operations correspond exactly to the primitive operations on {@link Path}, such as
- * {@link Path#moveTo(float, float)} and {@link Path#lineTo(float, float)}.
+ * {@link Path#moveTo(float, float)} and {@link Path#lineTo(float, float)}, except for
+ * {@link #VERB_DONE}, which means that there are no more operations in this path.
*/
@Retention(SOURCE)
@IntDef({VERB_MOVE, VERB_LINE, VERB_QUAD, VERB_CONIC, VERB_CUBIC, VERB_CLOSE, VERB_DONE})
@@ -109,7 +108,6 @@ public class PathIterator implements Iterator {
* @throws ArrayIndexOutOfBoundsException if the points array is too small
* @throws ConcurrentModificationException if the underlying path was modified
* since this iterator was created.
- * @throws NoSuchElementException if the iteration has no more elements
*/
@NonNull
public @Verb int next(@NonNull float[] points, int offset) {
@@ -126,8 +124,8 @@ public class PathIterator implements Iterator {
/**
* Returns true if the there are more elements in this iterator to be returned.
* A return value of false means there are no more elements, and an
- * ensuing call to {@link #next()} or {@link #next(float[], int)} )} will throw a
- * {@link NoSuchElementException}.
+ * ensuing call to {@link #next()} or {@link #next(float[], int)} )} will return
+ * {@link #VERB_DONE}.
*
* @return true if there are more elements to be iterated through, false otherwise
* @throws ConcurrentModificationException if the underlying path was modified
@@ -135,14 +133,10 @@ public class PathIterator implements Iterator {
*/
@Override
public boolean hasNext() {
- try {
- if (mCachedVerb == -1) {
- mCachedVerb = nextInternal();
- }
- return true;
- } catch (NoSuchElementException e) {
- return false;
+ if (mCachedVerb == -1) {
+ mCachedVerb = nextInternal();
}
+ return mCachedVerb != VERB_DONE;
}
/**
@@ -171,12 +165,13 @@ public class PathIterator implements Iterator {
* is helfpul for managing the cached segment used by {@link #hasNext()}.
*
* @return the segment to be returned by {@link #next()}
- * @throws NoSuchElementException if the iteration has no more elements
+ * @throws ConcurrentModificationException if the underlying path was modified
+ * since this iterator was created.
*/
@NonNull
private @Verb int nextInternal() {
if (mDone) {
- throw new NoSuchElementException("No more path segments to iterate");
+ return VERB_DONE;
}
if (mPathGenerationId != mPath.getGenerationId()) {
throw new ConcurrentModificationException(
@@ -198,7 +193,6 @@ public class PathIterator implements Iterator {
* requires a little more manual effort to use.
*
* @return the next segment in this iterator
- * @throws NoSuchElementException if the iteration has no more elements
* @throws ConcurrentModificationException if the underlying path was modified
* since this iterator was created.
*/
--
cgit v1.2.3-59-g8ed1b
From 7612c613c9a5d41892a201eafd4949424497fb5f Mon Sep 17 00:00:00 2001
From: Nader Jawad
Date: Tue, 15 Nov 2022 10:55:54 -0800
Subject: Created HardwareBufferRenderer to support rendering into
HardwareBuffer targets.
Relnote: "Created HardwareBufferRenderer API to handle
rendering a single frame into a HardwareBuffer target."
Refactored dlsym logic for dynamically resolving AHardwareBuffer
methods to be shared across multiple locations.
Bug: 255692581
Test: Created HardwareBufferRendererTests
Change-Id: I749b5d763a9ee580abc2d6cc87bd94a46b7abdd9
---
core/api/current.txt | 23 ++
core/java/android/hardware/SyncFence.java | 22 +-
core/jni/LayoutlibLoader.cpp | 1 +
.../android/graphics/HardwareBufferRenderer.java | 390 +++++++++++++++++++++
libs/hwui/Android.bp | 2 +
libs/hwui/apex/jni_runtime.cpp | 3 +
libs/hwui/jni/Bitmap.cpp | 46 +--
libs/hwui/jni/GraphicsJNI.h | 31 +-
libs/hwui/jni/HardwareBufferHelpers.cpp | 68 ++++
libs/hwui/jni/HardwareBufferHelpers.h | 38 ++
libs/hwui/jni/JvmErrorReporter.h | 44 +++
.../android_graphics_HardwareBufferRenderer.cpp | 177 ++++++++++
.../hwui/jni/android_graphics_HardwareRenderer.cpp | 65 +---
libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp | 67 +++-
libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h | 16 +-
libs/hwui/pipeline/skia/SkiaPipeline.cpp | 25 ++
libs/hwui/pipeline/skia/SkiaPipeline.h | 11 +
libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp | 48 ++-
libs/hwui/pipeline/skia/SkiaVulkanPipeline.h | 24 +-
libs/hwui/renderthread/CanvasContext.cpp | 20 +-
libs/hwui/renderthread/CanvasContext.h | 12 +-
libs/hwui/renderthread/DrawFrameTask.cpp | 14 +-
libs/hwui/renderthread/DrawFrameTask.h | 13 +
.../hwui/renderthread/HardwareBufferRenderParams.h | 62 ++++
libs/hwui/renderthread/IRenderPipeline.h | 18 +-
libs/hwui/renderthread/RenderProxy.cpp | 16 +
libs/hwui/renderthread/RenderProxy.h | 4 +-
libs/hwui/tests/unit/CanvasContextTests.cpp | 2 +-
tests/HwAccelerationTest/AndroidManifest.xml | 9 +
.../test/hwui/HardwareBufferRendererActivity.java | 86 +++++
30 files changed, 1200 insertions(+), 157 deletions(-)
create mode 100644 graphics/java/android/graphics/HardwareBufferRenderer.java
create mode 100644 libs/hwui/jni/HardwareBufferHelpers.cpp
create mode 100644 libs/hwui/jni/HardwareBufferHelpers.h
create mode 100644 libs/hwui/jni/JvmErrorReporter.h
create mode 100644 libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
create mode 100644 libs/hwui/renderthread/HardwareBufferRenderParams.h
create mode 100644 tests/HwAccelerationTest/src/com/android/test/hwui/HardwareBufferRendererActivity.java
(limited to 'graphics/java')
diff --git a/core/api/current.txt b/core/api/current.txt
index 5dd1b3938f40..31659e435915 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -15080,6 +15080,29 @@ package android.graphics {
ctor @Deprecated public EmbossMaskFilter(float[], float, float, float);
}
+ public class HardwareBufferRenderer implements java.lang.AutoCloseable {
+ ctor public HardwareBufferRenderer(@NonNull android.hardware.HardwareBuffer);
+ method public void close();
+ method public boolean isClosed();
+ method @NonNull public android.graphics.HardwareBufferRenderer.RenderRequest obtainRenderRequest();
+ method public void setContentRoot(@Nullable android.graphics.RenderNode);
+ method public void setLightSourceAlpha(@FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float);
+ method public void setLightSourceGeometry(float, float, @FloatRange(from=0.0f) float, @FloatRange(from=0.0f) float);
+ }
+
+ public final class HardwareBufferRenderer.RenderRequest {
+ method public void draw(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer);
+ method @NonNull public android.graphics.HardwareBufferRenderer.RenderRequest setBufferTransform(int);
+ method @NonNull public android.graphics.HardwareBufferRenderer.RenderRequest setColorSpace(@Nullable android.graphics.ColorSpace);
+ }
+
+ public static final class HardwareBufferRenderer.RenderResult {
+ method @NonNull public android.hardware.SyncFence getFence();
+ method public int getStatus();
+ field public static final int ERROR_UNKNOWN = 1; // 0x1
+ field public static final int SUCCESS = 0; // 0x0
+ }
+
public class HardwareRenderer {
ctor public HardwareRenderer();
method public void clearContent();
diff --git a/core/java/android/hardware/SyncFence.java b/core/java/android/hardware/SyncFence.java
index 166001347bd4..d6052cd4c67f 100644
--- a/core/java/android/hardware/SyncFence.java
+++ b/core/java/android/hardware/SyncFence.java
@@ -87,8 +87,8 @@ public final class SyncFence implements AutoCloseable, Parcelable {
// is well worth making.
private final Runnable mCloser;
- private SyncFence(@NonNull ParcelFileDescriptor wrapped) {
- mNativePtr = nCreate(wrapped.detachFd());
+ private SyncFence(int fileDescriptor) {
+ mNativePtr = nCreate(fileDescriptor);
mCloser = sRegistry.registerNativeAllocation(this, mNativePtr);
}
@@ -136,14 +136,26 @@ public final class SyncFence implements AutoCloseable, Parcelable {
}
/**
- * Create a new SyncFence wrapped around another descriptor. By default, all method calls are
- * delegated to the wrapped descriptor.
+ * Create a new SyncFence wrapped around another {@link ParcelFileDescriptor}. By default, all
+ * method calls are delegated to the wrapped descriptor. This takes ownership of the
+ * {@link ParcelFileDescriptor}.
*
* @param wrapped The descriptor to be wrapped.
* @hide
*/
public static @NonNull SyncFence create(@NonNull ParcelFileDescriptor wrapped) {
- return new SyncFence(wrapped);
+ return new SyncFence(wrapped.detachFd());
+ }
+
+ /**
+ * Create a new SyncFence wrapped around another descriptor. The returned {@link SyncFence}
+ * instance takes ownership of the file descriptor.
+ *
+ * @param fileDescriptor The descriptor to be wrapped.
+ * @hide
+ */
+ public static @NonNull SyncFence adopt(int fileDescriptor) {
+ return new SyncFence(fileDescriptor);
}
/**
diff --git a/core/jni/LayoutlibLoader.cpp b/core/jni/LayoutlibLoader.cpp
index 93ba23bfdf84..d7cbf74d421a 100644
--- a/core/jni/LayoutlibLoader.cpp
+++ b/core/jni/LayoutlibLoader.cpp
@@ -102,6 +102,7 @@ extern int register_android_view_KeyCharacterMap(JNIEnv* env);
extern int register_android_view_KeyEvent(JNIEnv* env);
extern int register_android_view_MotionEvent(JNIEnv* env);
extern int register_android_view_ThreadedRenderer(JNIEnv* env);
+extern int register_android_graphics_HardwareBufferRenderer(JNIEnv* env);
extern int register_android_view_VelocityTracker(JNIEnv* env);
extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env);
diff --git a/graphics/java/android/graphics/HardwareBufferRenderer.java b/graphics/java/android/graphics/HardwareBufferRenderer.java
new file mode 100644
index 000000000000..361dc594f2c6
--- /dev/null
+++ b/graphics/java/android/graphics/HardwareBufferRenderer.java
@@ -0,0 +1,390 @@
+/*
+ * Copyright (C) 2022 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.graphics.ColorSpace.Named;
+import android.hardware.HardwareBuffer;
+import android.hardware.SyncFence;
+import android.view.SurfaceControl;
+
+import libcore.util.NativeAllocationRegistry;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Creates an instance of a hardware-accelerated renderer. This is used to render a scene built
+ * from {@link RenderNode}s to an output {@link HardwareBuffer}. There can be as many
+ * HardwareBufferRenderer instances as desired.
+ *
+ * Resources & lifecycle
+ *
+ * All HardwareBufferRenderer and {@link HardwareRenderer} instances share a common render
+ * thread. Therefore HardwareBufferRenderer will share common resources and GPU utilization with
+ * hardware accelerated rendering initiated by the UI thread of an application.
+ * The render thread contains the GPU context & resources necessary to do GPU-accelerated
+ * rendering. As such, the first HardwareBufferRenderer created comes with the cost of also creating
+ * the associated GPU contexts, however each incremental HardwareBufferRenderer thereafter is fairly
+ * cheap. The expected usage is to have a HardwareBufferRenderer instance for every active {@link
+ * HardwareBuffer}.
+ *
+ * This is useful in situations where a scene built with {@link RenderNode}s can be consumed
+ * directly by the system compositor through
+ * {@link SurfaceControl.Transaction#setBuffer(SurfaceControl, HardwareBuffer)}.
+ *
+ * HardwareBufferRenderer will never clear contents before each draw invocation so previous contents
+ * in the {@link HardwareBuffer} target will be preserved across renders.
+ */
+public class HardwareBufferRenderer implements AutoCloseable {
+
+ private static final ColorSpace DEFAULT_COLORSPACE = ColorSpace.get(Named.SRGB);
+
+ private static class HardwareBufferRendererHolder {
+ public static final NativeAllocationRegistry REGISTRY =
+ NativeAllocationRegistry.createMalloced(
+ HardwareBufferRenderer.class.getClassLoader(), nGetFinalizer());
+ }
+
+ private final HardwareBuffer mHardwareBuffer;
+ private final RenderRequest mRenderRequest;
+ private final RenderNode mRootNode;
+ private final Runnable mCleaner;
+
+ private long mProxy;
+
+ /**
+ * Creates a new instance of {@link HardwareBufferRenderer} with the provided {@link
+ * HardwareBuffer} as the output of the rendered scene.
+ */
+ public HardwareBufferRenderer(@NonNull HardwareBuffer buffer) {
+ RenderNode rootNode = RenderNode.adopt(nCreateRootRenderNode());
+ rootNode.setClipToBounds(false);
+ mProxy = nCreateHardwareBufferRenderer(buffer, rootNode.mNativeRenderNode);
+ mCleaner = HardwareBufferRendererHolder.REGISTRY.registerNativeAllocation(this, mProxy);
+ mRenderRequest = new RenderRequest();
+ mRootNode = rootNode;
+ mHardwareBuffer = buffer;
+ }
+
+ /**
+ * 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 RenderNodes
+ * contained within the content node, will be applied whenever a new {@link RenderRequest} is
+ * issued via {@link #obtainRenderRequest()} and {@link RenderRequest#draw(Executor,
+ * Consumer)}.
+ *
+ * @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();
+ }
+
+ /**
+ * Returns a {@link RenderRequest} that can be used to render into the provided {@link
+ * HardwareBuffer}. This is used to synchronize the RenderNode content provided by {@link
+ * #setContentRoot(RenderNode)}.
+ *
+ * @return An instance of {@link RenderRequest}. The instance may be reused for every frame, so
+ * the caller should not hold onto it for longer than a single render request.
+ */
+ @NonNull
+ public RenderRequest obtainRenderRequest() {
+ mRenderRequest.reset();
+ return mRenderRequest;
+ }
+
+ /**
+ * Returns if the {@link HardwareBufferRenderer} has already been closed. That is
+ * {@link HardwareBufferRenderer#close()} has been invoked.
+ * @return True if the {@link HardwareBufferRenderer} has been closed, false otherwise.
+ */
+ public boolean isClosed() {
+ return mProxy == 0L;
+ }
+
+ /**
+ * Releases the resources associated with this {@link HardwareBufferRenderer} instance. **Note**
+ * this does not call {@link HardwareBuffer#close()} on the provided {@link HardwareBuffer}
+ * instance
+ */
+ @Override
+ public void close() {
+ // Note we explicitly call this only here to clean-up potential animator state
+ // This is not done as part of the NativeAllocationRegistry as it would invoke animator
+ // callbacks on the wrong thread
+ nDestroyRootRenderNode(mRootNode.mNativeRenderNode);
+ if (mProxy != 0L) {
+ mCleaner.run();
+ mProxy = 0L;
+ }
+ }
+
+ /**
+ * Sets the center of the light source. The light source point controls the directionality and
+ * shape of shadows rendered by RenderNode Z & elevation.
+ *
+ * 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.
+ *
+ *
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. If unsure, a reasonable default
+ * is 'displayWidth / 2f - windowLeft'.
+ * @param lightY The Y position of the light source. If unsure, a reasonable default
+ * is '0 - windowTop'
+ * @param lightZ The Z position of the light source. Must be >= 0. If unsure, a reasonable
+ * default is 600dp.
+ * @param lightRadius The radius of the light source. Smaller radius will have sharper edges,
+ * larger radius will have softer shadows. If unsure, a reasonable default is 800 dp.
+ */
+ public void setLightSourceGeometry(
+ float lightX,
+ float lightY,
+ @FloatRange(from = 0f) float lightZ,
+ @FloatRange(from = 0f) float lightRadius
+ ) {
+ validateFinite(lightX, "lightX");
+ validateFinite(lightY, "lightY");
+ validatePositive(lightZ, "lightZ");
+ validatePositive(lightRadius, "lightRadius");
+ nSetLightGeometry(mProxy, 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.
+ *
+ *
These values are typically provided by the current theme, see
+ * {@link android.R.attr#spotShadowAlpha} and {@link android.R.attr#ambientShadowAlpha}.
+ *
+ *
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(mProxy, ambientShadowAlpha, spotShadowAlpha);
+ }
+
+ /**
+ * Class that contains data regarding the result of the render request.
+ * Consumers are to wait on the provided {@link SyncFence} before consuming the HardwareBuffer
+ * provided to {@link HardwareBufferRenderer} as well as verify that the status returned by
+ * {@link RenderResult#getStatus()} returns {@link RenderResult#SUCCESS}.
+ */
+ public static final class RenderResult {
+
+ /**
+ * Render request was completed successfully
+ */
+ public static final int SUCCESS = 0;
+
+ /**
+ * Render request failed with an unknown error
+ */
+ public static final int ERROR_UNKNOWN = 1;
+
+ /** @hide **/
+ @IntDef(value = {SUCCESS, ERROR_UNKNOWN})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RenderResultStatus{}
+
+ private final SyncFence mFence;
+ private final int mResultStatus;
+
+ private RenderResult(@NonNull SyncFence fence, @RenderResultStatus int resultStatus) {
+ mFence = fence;
+ mResultStatus = resultStatus;
+ }
+
+ @NonNull
+ public SyncFence getFence() {
+ return mFence;
+ }
+
+ @RenderResultStatus
+ public int getStatus() {
+ return mResultStatus;
+ }
+ }
+
+ /**
+ * Sets the parameters that can be used to control a render request for a {@link
+ * HardwareBufferRenderer}. This is not thread-safe and must not be held on to for longer than a
+ * single request.
+ */
+ public final class RenderRequest {
+
+ private ColorSpace mColorSpace = DEFAULT_COLORSPACE;
+ private int mTransform = SurfaceControl.BUFFER_TRANSFORM_IDENTITY;
+
+ private RenderRequest() { }
+
+ /**
+ * Syncs the RenderNode tree to the render thread and requests content to be drawn. This
+ * {@link RenderRequest} instance should no longer be used after calling this method. The
+ * system internally may reuse instances of {@link RenderRequest} to reduce allocation
+ * churn.
+ *
+ * @param executor Executor used to deliver callbacks
+ * @param renderCallback Callback invoked when rendering is complete. This includes a
+ * {@link RenderResult} that provides a {@link SyncFence} that should be waited upon for
+ * completion before consuming the rendered output in the provided {@link HardwareBuffer}
+ * instance.
+ *
+ * @throws IllegalStateException if attempt to draw is made when
+ * {@link HardwareBufferRenderer#isClosed()} returns true
+ */
+ public void draw(
+ @NonNull Executor executor,
+ @NonNull Consumer renderCallback
+ ) {
+ Consumer wrapped = consumable -> executor.execute(
+ () -> renderCallback.accept(consumable));
+ if (!isClosed()) {
+ nRender(
+ mProxy,
+ mTransform,
+ mHardwareBuffer.getWidth(),
+ mHardwareBuffer.getHeight(),
+ mColorSpace.getNativeInstance(),
+ wrapped);
+ } else {
+ throw new IllegalStateException("Attempt to draw with a HardwareBufferRenderer "
+ + "instance that has already been closed");
+ }
+ }
+
+ private void reset() {
+ mColorSpace = DEFAULT_COLORSPACE;
+ mTransform = SurfaceControl.BUFFER_TRANSFORM_IDENTITY;
+ }
+
+ /**
+ * Configures the color space which the content should be rendered in. This affects
+ * how the framework will interpret the color at each pixel. The color space provided here
+ * must be non-null, RGB based and leverage an ICC parametric curve. The min/max values
+ * of the components should not reduce the numerical range compared to the previously
+ * assigned color space. If left unspecified, the default color space of SRGB will be used.
+ *
+ * @param colorSpace The color space the content should be rendered in. If null is provided
+ * the default of SRGB will be used.
+ */
+ @NonNull
+ public RenderRequest setColorSpace(@Nullable ColorSpace colorSpace) {
+ if (colorSpace == null) {
+ mColorSpace = DEFAULT_COLORSPACE;
+ } else {
+ mColorSpace = colorSpace;
+ }
+ return this;
+ }
+
+ /**
+ * Specifies a transform to be applied before content is rendered. This is useful
+ * for pre-rotating content for the current display orientation to increase performance
+ * of displaying the associated buffer. This transformation will also adjust the light
+ * source position for the specified rotation.
+ * @see SurfaceControl.Transaction#setBufferTransform(SurfaceControl, int)
+ */
+ @NonNull
+ public RenderRequest setBufferTransform(
+ @SurfaceControl.BufferTransform int bufferTransform) {
+ boolean validTransform = bufferTransform == SurfaceControl.BUFFER_TRANSFORM_IDENTITY
+ || bufferTransform == SurfaceControl.BUFFER_TRANSFORM_ROTATE_90
+ || bufferTransform == SurfaceControl.BUFFER_TRANSFORM_ROTATE_180
+ || bufferTransform == SurfaceControl.BUFFER_TRANSFORM_ROTATE_270;
+ if (validTransform) {
+ mTransform = bufferTransform;
+ } else {
+ throw new IllegalArgumentException("Invalid transform provided, must be one of"
+ + "the SurfaceControl.BufferTransform values");
+ }
+ return this;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ /* package */
+ static native int nRender(long renderer, int transform, int width, int height, long colorSpace,
+ Consumer callback);
+
+ private static native long nCreateRootRenderNode();
+
+ private static native void nDestroyRootRenderNode(long rootRenderNode);
+
+ private static native long nCreateHardwareBufferRenderer(HardwareBuffer buffer,
+ long rootRenderNode);
+
+ private static native void nSetLightGeometry(long bufferRenderer, float lightX, float lightY,
+ float lightZ, float radius);
+
+ private static native void nSetLightAlpha(long nativeProxy, float ambientShadowAlpha,
+ float spotShadowAlpha);
+
+ private static native long nGetFinalizer();
+
+ // Called by native
+ private static void invokeRenderCallback(
+ @NonNull Consumer callback,
+ int fd,
+ int status
+ ) {
+ callback.accept(new RenderResult(SyncFence.adopt(fd), status));
+ }
+
+ 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 validateFinite(float f, String argumentName) {
+ if (!Float.isFinite(f)) {
+ throw new IllegalArgumentException(argumentName + " must be finite, given=" + f);
+ }
+ }
+
+ 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);
+ }
+ }
+}
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 3e3d77b89a9a..59e4b7acdba7 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -329,6 +329,7 @@ cc_defaults {
"jni/android_util_PathParser.cpp",
"jni/Bitmap.cpp",
+ "jni/HardwareBufferHelpers.cpp",
"jni/BitmapFactory.cpp",
"jni/ByteBufferStreamAdaptor.cpp",
"jni/Camera.cpp",
@@ -397,6 +398,7 @@ cc_defaults {
"jni/AnimatedImageDrawable.cpp",
"jni/android_graphics_TextureLayer.cpp",
"jni/android_graphics_HardwareRenderer.cpp",
+ "jni/android_graphics_HardwareBufferRenderer.cpp",
"jni/BitmapRegionDecoder.cpp",
"jni/GIFMovie.cpp",
"jni/GraphicsStatsService.cpp",
diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp
index e6cfa7bcaf70..f57d80c496ad 100644
--- a/libs/hwui/apex/jni_runtime.cpp
+++ b/libs/hwui/apex/jni_runtime.cpp
@@ -83,6 +83,7 @@ extern int register_android_util_PathParser(JNIEnv* env);
extern int register_android_view_DisplayListCanvas(JNIEnv* env);
extern int register_android_view_RenderNode(JNIEnv* env);
extern int register_android_view_ThreadedRenderer(JNIEnv* env);
+extern int register_android_graphics_HardwareBufferRenderer(JNIEnv* env);
#ifdef NDEBUG
#define REG_JNI(name) { name }
@@ -151,6 +152,8 @@ extern int register_android_view_ThreadedRenderer(JNIEnv* env);
REG_JNI(register_android_util_PathParser),
REG_JNI(register_android_view_RenderNode),
REG_JNI(register_android_view_DisplayListCanvas),
+ REG_JNI(register_android_graphics_HardwareBufferRenderer),
+
REG_JNI(register_android_view_ThreadedRenderer),
};
diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp
index 94cea65897cf..540abec38ebc 100755
--- a/libs/hwui/jni/Bitmap.cpp
+++ b/libs/hwui/jni/Bitmap.cpp
@@ -2,48 +2,41 @@
#define LOG_TAG "Bitmap"
#include "Bitmap.h"
+#include
+#include
+
+#include "CreateJavaOutputStreamAdaptor.h"
#include "GraphicsJNI.h"
+#include "HardwareBufferHelpers.h"
#include "SkBitmap.h"
#include "SkBlendMode.h"
#include "SkCanvas.h"
#include "SkColor.h"
#include "SkColorSpace.h"
#include "SkData.h"
-#include "SkImageEncoder.h"
#include "SkImageInfo.h"
#include "SkPaint.h"
-#include "SkPixelRef.h"
#include "SkPixmap.h"
#include "SkPoint.h"
#include "SkRefCnt.h"
#include "SkStream.h"
#include "SkTypes.h"
-#include "SkWebpEncoder.h"
-
-
#include "android_nio_utils.h"
-#include "CreateJavaOutputStreamAdaptor.h"
-#include
-#include
-#include
#ifdef __ANDROID__ // Layoutlib does not support graphic buffer, parcel or render thread
#include
#include
#include
#include
-#include
-#include
#include
-#include
#include
#include
#endif
#include
#include
+
#include
-#include
#define DEBUG_PARCEL 0
@@ -1197,18 +1190,11 @@ static jobject Bitmap_copyPreserveInternalConfig(JNIEnv* env, jobject, jlong bit
return createBitmap(env, bitmap.release(), getPremulBitmapCreateFlags(false));
}
-#ifdef __ANDROID__ // Layoutlib does not support graphic buffer
-typedef AHardwareBuffer* (*AHB_from_HB)(JNIEnv*, jobject);
-AHB_from_HB AHardwareBuffer_fromHardwareBuffer;
-
-typedef jobject (*AHB_to_HB)(JNIEnv*, AHardwareBuffer*);
-AHB_to_HB AHardwareBuffer_toHardwareBuffer;
-#endif
-
static jobject Bitmap_wrapHardwareBufferBitmap(JNIEnv* env, jobject, jobject hardwareBuffer,
jlong colorSpacePtr) {
#ifdef __ANDROID__ // Layoutlib does not support graphic buffer
- AHardwareBuffer* buffer = AHardwareBuffer_fromHardwareBuffer(env, hardwareBuffer);
+ AHardwareBuffer* buffer = uirenderer::HardwareBufferHelpers::AHardwareBuffer_fromHardwareBuffer(
+ env, hardwareBuffer);
sk_sp bitmap = Bitmap::createFrom(buffer,
GraphicsJNI::getNativeColorSpace(colorSpacePtr));
if (!bitmap.get()) {
@@ -1231,7 +1217,8 @@ static jobject Bitmap_getHardwareBuffer(JNIEnv* env, jobject, jlong bitmapPtr) {
}
Bitmap& bitmap = bitmapHandle->bitmap();
- return AHardwareBuffer_toHardwareBuffer(env, bitmap.hardwareBuffer());
+ return uirenderer::HardwareBufferHelpers::AHardwareBuffer_toHardwareBuffer(
+ env, bitmap.hardwareBuffer());
#else
return nullptr;
#endif
@@ -1329,18 +1316,7 @@ int register_android_graphics_Bitmap(JNIEnv* env)
gBitmap_nativePtr = GetFieldIDOrDie(env, gBitmap_class, "mNativePtr", "J");
gBitmap_constructorMethodID = GetMethodIDOrDie(env, gBitmap_class, "", "(JIIIZ[BLandroid/graphics/NinePatch$InsetStruct;Z)V");
gBitmap_reinitMethodID = GetMethodIDOrDie(env, gBitmap_class, "reinit", "(IIZ)V");
-
-#ifdef __ANDROID__ // Layoutlib does not support graphic buffer or parcel
- void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
- AHardwareBuffer_fromHardwareBuffer =
- (AHB_from_HB)dlsym(handle_, "AHardwareBuffer_fromHardwareBuffer");
- LOG_ALWAYS_FATAL_IF(AHardwareBuffer_fromHardwareBuffer == nullptr,
- "Failed to find required symbol AHardwareBuffer_fromHardwareBuffer!");
-
- AHardwareBuffer_toHardwareBuffer = (AHB_to_HB)dlsym(handle_, "AHardwareBuffer_toHardwareBuffer");
- LOG_ALWAYS_FATAL_IF(AHardwareBuffer_toHardwareBuffer == nullptr,
- " Failed to find required symbol AHardwareBuffer_toHardwareBuffer!");
-#endif
+ uirenderer::HardwareBufferHelpers::init();
return android::RegisterMethodsOrDie(env, "android/graphics/Bitmap", gBitmapMethods,
NELEM(gBitmapMethods));
}
diff --git a/libs/hwui/jni/GraphicsJNI.h b/libs/hwui/jni/GraphicsJNI.h
index 085a905abaf8..c4a61ccd1e5f 100644
--- a/libs/hwui/jni/GraphicsJNI.h
+++ b/libs/hwui/jni/GraphicsJNI.h
@@ -2,19 +2,18 @@
#define _ANDROID_GRAPHICS_GRAPHICS_JNI_H_
#include
+#include
+#include
-#include "Bitmap.h"
#include "BRDAllocator.h"
+#include "Bitmap.h"
#include "SkBitmap.h"
#include "SkCodec.h"
-#include "SkPixelRef.h"
+#include "SkColorSpace.h"
#include "SkMallocPixelRef.h"
+#include "SkPixelRef.h"
#include "SkPoint.h"
#include "SkRect.h"
-#include "SkColorSpace.h"
-#include
-#include
-
#include "graphics_jni_helpers.h"
class SkCanvas;
@@ -335,6 +334,26 @@ private:
int fLen;
};
+class JGlobalRefHolder {
+public:
+ JGlobalRefHolder(JavaVM* vm, jobject object) : mVm(vm), mObject(object) {}
+
+ virtual ~JGlobalRefHolder() {
+ GraphicsJNI::getJNIEnv()->DeleteGlobalRef(mObject);
+ mObject = nullptr;
+ }
+
+ jobject object() { return mObject; }
+ JavaVM* vm() { return mVm; }
+
+private:
+ JGlobalRefHolder(const JGlobalRefHolder&) = delete;
+ void operator=(const JGlobalRefHolder&) = delete;
+
+ JavaVM* mVm;
+ jobject mObject;
+};
+
void doThrowNPE(JNIEnv* env);
void doThrowAIOOBE(JNIEnv* env); // Array Index Out Of Bounds Exception
void doThrowIAE(JNIEnv* env, const char* msg = NULL); // Illegal Argument
diff --git a/libs/hwui/jni/HardwareBufferHelpers.cpp b/libs/hwui/jni/HardwareBufferHelpers.cpp
new file mode 100644
index 000000000000..7e3f771b6b3d
--- /dev/null
+++ b/libs/hwui/jni/HardwareBufferHelpers.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include "HardwareBufferHelpers.h"
+
+#include
+#include
+
+#ifdef __ANDROID__
+typedef AHardwareBuffer* (*AHB_from_HB)(JNIEnv*, jobject);
+typedef jobject (*AHB_to_HB)(JNIEnv*, AHardwareBuffer*);
+static AHB_from_HB fromHardwareBuffer = nullptr;
+static AHB_to_HB toHardwareBuffer = nullptr;
+#endif
+
+void android::uirenderer::HardwareBufferHelpers::init() {
+#ifdef __ANDROID__ // Layoutlib does not support graphic buffer or parcel
+ void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
+ fromHardwareBuffer = (AHB_from_HB)dlsym(handle_, "AHardwareBuffer_fromHardwareBuffer");
+ LOG_ALWAYS_FATAL_IF(fromHardwareBuffer == nullptr,
+ "Failed to find required symbol AHardwareBuffer_fromHardwareBuffer!");
+
+ toHardwareBuffer = (AHB_to_HB)dlsym(handle_, "AHardwareBuffer_toHardwareBuffer");
+ LOG_ALWAYS_FATAL_IF(toHardwareBuffer == nullptr,
+ " Failed to find required symbol AHardwareBuffer_toHardwareBuffer!");
+#endif
+}
+
+AHardwareBuffer* android::uirenderer::HardwareBufferHelpers::AHardwareBuffer_fromHardwareBuffer(
+ JNIEnv* env, jobject hardwarebuffer) {
+#ifdef __ANDROID__
+ LOG_ALWAYS_FATAL_IF(fromHardwareBuffer == nullptr,
+ "Failed to find symbol AHardwareBuffer_fromHardwareBuffer, did you forget "
+ "to call HardwareBufferHelpers::init?");
+ return fromHardwareBuffer(env, hardwarebuffer);
+#else
+ ALOGE("ERROR attempting to invoke AHardwareBuffer_fromHardwareBuffer on non Android "
+ "configuration");
+ return nullptr;
+#endif
+}
+
+jobject android::uirenderer::HardwareBufferHelpers::AHardwareBuffer_toHardwareBuffer(
+ JNIEnv* env, AHardwareBuffer* ahardwarebuffer) {
+#ifdef __ANDROID__
+ LOG_ALWAYS_FATAL_IF(toHardwareBuffer == nullptr,
+ "Failed to find symbol AHardwareBuffer_toHardwareBuffer, did you forget to "
+ "call HardwareBufferHelpers::init?");
+ return toHardwareBuffer(env, ahardwarebuffer);
+#else
+ ALOGE("ERROR attempting to invoke AHardwareBuffer_toHardwareBuffer on non Android "
+ "configuration");
+ return nullptr;
+#endif
+}
\ No newline at end of file
diff --git a/libs/hwui/jni/HardwareBufferHelpers.h b/libs/hwui/jni/HardwareBufferHelpers.h
new file mode 100644
index 000000000000..326babfb0b34
--- /dev/null
+++ b/libs/hwui/jni/HardwareBufferHelpers.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+#ifndef HARDWAREBUFFER_JNI_HELPERS_H
+#define HARDWAREBUFFER_JNI_HELPERS_H
+
+#include
+#include
+
+namespace android {
+namespace uirenderer {
+
+class HardwareBufferHelpers {
+public:
+ static void init();
+ static AHardwareBuffer* AHardwareBuffer_fromHardwareBuffer(JNIEnv*, jobject);
+ static jobject AHardwareBuffer_toHardwareBuffer(JNIEnv*, AHardwareBuffer*);
+
+private:
+ HardwareBufferHelpers() = default; // not to be instantiated
+};
+
+} // namespace uirenderer
+} // namespace android
+
+#endif // HARDWAREBUFFER_JNI_HELPERS_H
\ No newline at end of file
diff --git a/libs/hwui/jni/JvmErrorReporter.h b/libs/hwui/jni/JvmErrorReporter.h
new file mode 100644
index 000000000000..5e10b9d93275
--- /dev/null
+++ b/libs/hwui/jni/JvmErrorReporter.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+#ifndef JVMERRORREPORTER_H
+#define JVMERRORREPORTER_H
+
+#include
+#include
+#include
+
+#include "GraphicsJNI.h"
+
+namespace android {
+namespace uirenderer {
+
+class JvmErrorReporter : public android::uirenderer::ErrorHandler {
+public:
+ JvmErrorReporter(JNIEnv* env) { env->GetJavaVM(&mVm); }
+
+ virtual void onError(const std::string& message) override {
+ JNIEnv* env = GraphicsJNI::getJNIEnv();
+ jniThrowException(env, "java/lang/IllegalStateException", message.c_str());
+ }
+
+private:
+ JavaVM* mVm;
+};
+
+} // namespace uirenderer
+} // namespace android
+
+#endif // JVMERRORREPORTER_H
diff --git a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
new file mode 100644
index 000000000000..4886fdd7ac67
--- /dev/null
+++ b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
@@ -0,0 +1,177 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "HardwareBufferRenderer"
+#define ATRACE_TAG ATRACE_TAG_VIEW
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "HardwareBufferHelpers.h"
+#include "JvmErrorReporter.h"
+
+namespace android {
+
+using namespace android::uirenderer;
+using namespace android::uirenderer::renderthread;
+
+struct {
+ jclass clazz;
+ jmethodID invokeRenderCallback;
+} gHardwareBufferRendererClassInfo;
+
+static RenderCallback createRenderCallback(JNIEnv* env, jobject releaseCallback) {
+ if (releaseCallback == nullptr) return nullptr;
+
+ JavaVM* vm = nullptr;
+ LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Unable to get Java VM");
+ auto globalCallbackRef =
+ std::make_shared(vm, env->NewGlobalRef(releaseCallback));
+ return [globalCallbackRef](android::base::unique_fd&& fd, int status) {
+ GraphicsJNI::getJNIEnv()->CallStaticVoidMethod(
+ gHardwareBufferRendererClassInfo.clazz,
+ gHardwareBufferRendererClassInfo.invokeRenderCallback, globalCallbackRef->object(),
+ reinterpret_cast(fd.release()), reinterpret_cast(status));
+ };
+}
+
+static long android_graphics_HardwareBufferRenderer_createRootNode(JNIEnv* env, jobject) {
+ auto* node = new RootRenderNode(std::make_unique(env));
+ node->incStrong(nullptr);
+ node->setName("RootRenderNode");
+ return reinterpret_cast(node);
+}
+
+static void android_graphics_hardwareBufferRenderer_destroyRootNode(JNIEnv*, jobject,
+ jlong renderNodePtr) {
+ auto* node = reinterpret_cast(renderNodePtr);
+ node->destroy();
+}
+
+static long android_graphics_HardwareBufferRenderer_create(JNIEnv* env, jobject, jobject buffer,
+ jlong renderNodePtr) {
+ auto* hardwareBuffer = HardwareBufferHelpers::AHardwareBuffer_fromHardwareBuffer(env, buffer);
+ auto* rootRenderNode = reinterpret_cast(renderNodePtr);
+ ContextFactoryImpl factory(rootRenderNode);
+ auto* proxy = new RenderProxy(true, rootRenderNode, &factory);
+ proxy->setHardwareBuffer(hardwareBuffer);
+ return (jlong)proxy;
+}
+
+static void HardwareBufferRenderer_destroy(jobject renderProxy) {
+ auto* proxy = reinterpret_cast(renderProxy);
+ delete proxy;
+}
+
+static SkMatrix createMatrixFromBufferTransform(SkScalar width, SkScalar height, int transform) {
+ auto matrix = SkMatrix();
+ switch (transform) {
+ case ANATIVEWINDOW_TRANSFORM_ROTATE_90:
+ matrix.setRotate(90);
+ matrix.postTranslate(width, 0);
+ break;
+ case ANATIVEWINDOW_TRANSFORM_ROTATE_180:
+ matrix.setRotate(180);
+ matrix.postTranslate(width, height);
+ break;
+ case ANATIVEWINDOW_TRANSFORM_ROTATE_270:
+ matrix.setRotate(270);
+ matrix.postTranslate(0, width);
+ break;
+ default:
+ ALOGE("Invalid transform provided. Transform should be validated from"
+ "the java side. Leveraging identity transform as a fallback");
+ [[fallthrough]];
+ case ANATIVEWINDOW_TRANSFORM_IDENTITY:
+ break;
+ }
+ return matrix;
+}
+
+static int android_graphics_HardwareBufferRenderer_render(JNIEnv* env, jobject, jobject renderProxy,
+ jint transform, jint width, jint height,
+ jlong colorspacePtr, jobject consumer) {
+ auto* proxy = reinterpret_cast(renderProxy);
+ auto skWidth = static_cast(width);
+ auto skHeight = static_cast(height);
+ auto matrix = createMatrixFromBufferTransform(skWidth, skHeight, transform);
+ auto colorSpace = GraphicsJNI::getNativeColorSpace(colorspacePtr);
+ proxy->setHardwareBufferRenderParams(
+ HardwareBufferRenderParams(matrix, colorSpace, createRenderCallback(env, consumer)));
+ return proxy->syncAndDrawFrame();
+}
+
+static void android_graphics_HardwareBufferRenderer_setLightGeometry(JNIEnv*, jobject,
+ jobject renderProxyPtr,
+ jfloat lightX, jfloat lightY,
+ jfloat lightZ,
+ jfloat lightRadius) {
+ auto* proxy = reinterpret_cast(renderProxyPtr);
+ proxy->setLightGeometry((Vector3){lightX, lightY, lightZ}, lightRadius);
+}
+
+static void android_graphics_HardwareBufferRenderer_setLightAlpha(JNIEnv* env, jobject,
+ jobject renderProxyPtr,
+ jfloat ambientShadowAlpha,
+ jfloat spotShadowAlpha) {
+ auto* proxy = reinterpret_cast(renderProxyPtr);
+ proxy->setLightAlpha((uint8_t)(255 * ambientShadowAlpha), (uint8_t)(255 * spotShadowAlpha));
+}
+
+static jlong android_graphics_HardwareBufferRenderer_getFinalizer() {
+ return static_cast(reinterpret_cast(&HardwareBufferRenderer_destroy));
+}
+
+// ----------------------------------------------------------------------------
+// JNI Glue
+// ----------------------------------------------------------------------------
+
+const char* const kClassPathName = "android/graphics/HardwareBufferRenderer";
+
+static const JNINativeMethod gMethods[] = {
+ {"nCreateHardwareBufferRenderer", "(Landroid/hardware/HardwareBuffer;J)J",
+ (void*)android_graphics_HardwareBufferRenderer_create},
+ {"nRender", "(JIIIJLjava/util/function/Consumer;)I",
+ (void*)android_graphics_HardwareBufferRenderer_render},
+ {"nCreateRootRenderNode", "()J",
+ (void*)android_graphics_HardwareBufferRenderer_createRootNode},
+ {"nSetLightGeometry", "(JFFFF)V",
+ (void*)android_graphics_HardwareBufferRenderer_setLightGeometry},
+ {"nSetLightAlpha", "(JFF)V", (void*)android_graphics_HardwareBufferRenderer_setLightAlpha},
+ {"nGetFinalizer", "()J", (void*)android_graphics_HardwareBufferRenderer_getFinalizer},
+ {"nDestroyRootRenderNode", "(J)V",
+ (void*)android_graphics_hardwareBufferRenderer_destroyRootNode}};
+
+int register_android_graphics_HardwareBufferRenderer(JNIEnv* env) {
+ jclass hardwareBufferRendererClazz =
+ FindClassOrDie(env, "android/graphics/HardwareBufferRenderer");
+ gHardwareBufferRendererClassInfo.clazz = hardwareBufferRendererClazz;
+ gHardwareBufferRendererClassInfo.invokeRenderCallback =
+ GetStaticMethodIDOrDie(env, hardwareBufferRendererClazz, "invokeRenderCallback",
+ "(Ljava/util/function/Consumer;II)V");
+ HardwareBufferHelpers::init();
+ return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index 0663121a4027..47e2edb2ed0f 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -56,6 +56,7 @@
#include
#include
+#include "JvmErrorReporter.h"
#include "android_graphics_HardwareRendererObserver.h"
namespace android {
@@ -93,35 +94,12 @@ struct {
jmethodID getDestinationBitmap;
} gCopyRequest;
-static JNIEnv* getenv(JavaVM* vm) {
- JNIEnv* env;
- if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) {
- LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", vm);
- }
- return env;
-}
-
typedef ANativeWindow* (*ANW_fromSurface)(JNIEnv* env, jobject surface);
ANW_fromSurface fromSurface;
-class JvmErrorReporter : public ErrorHandler {
-public:
- JvmErrorReporter(JNIEnv* env) {
- env->GetJavaVM(&mVm);
- }
-
- virtual void onError(const std::string& message) override {
- JNIEnv* env = getenv(mVm);
- jniThrowException(env, "java/lang/IllegalStateException", message.c_str());
- }
-private:
- JavaVM* mVm;
-};
-
class FrameCommitWrapper : public LightRefBase {
public:
explicit FrameCommitWrapper(JNIEnv* env, jobject jobject) {
- env->GetJavaVM(&mVm);
mObject = env->NewGlobalRef(jobject);
LOG_ALWAYS_FATAL_IF(!mObject, "Failed to make global ref");
}
@@ -131,19 +109,18 @@ public:
void onFrameCommit(bool didProduceBuffer) {
if (mObject) {
ATRACE_FORMAT("frameCommit success=%d", didProduceBuffer);
- getenv(mVm)->CallVoidMethod(mObject, gFrameCommitCallback.onFrameCommit,
- didProduceBuffer);
+ GraphicsJNI::getJNIEnv()->CallVoidMethod(mObject, gFrameCommitCallback.onFrameCommit,
+ didProduceBuffer);
releaseObject();
}
}
private:
- JavaVM* mVm;
jobject mObject;
void releaseObject() {
if (mObject) {
- getenv(mVm)->DeleteGlobalRef(mObject);
+ GraphicsJNI::getJNIEnv()->DeleteGlobalRef(mObject);
mObject = nullptr;
}
}
@@ -443,26 +420,6 @@ static void android_view_ThreadedRenderer_forceDrawNextFrame(JNIEnv* env, jobjec
proxy->forceDrawNextFrame();
}
-class JGlobalRefHolder {
-public:
- JGlobalRefHolder(JavaVM* vm, jobject object) : mVm(vm), mObject(object) {}
-
- virtual ~JGlobalRefHolder() {
- getenv(mVm)->DeleteGlobalRef(mObject);
- mObject = nullptr;
- }
-
- jobject object() { return mObject; }
- JavaVM* vm() { return mVm; }
-
-private:
- JGlobalRefHolder(const JGlobalRefHolder&) = delete;
- void operator=(const JGlobalRefHolder&) = delete;
-
- JavaVM* mVm;
- jobject mObject;
-};
-
using TextureMap = std::unordered_map>;
struct PictureCaptureState {
@@ -578,7 +535,7 @@ static void android_view_ThreadedRenderer_setPictureCapturedCallbackJNI(JNIEnv*
auto pictureState = std::make_shared();
proxy->setPictureCapturedCallback([globalCallbackRef,
pictureState](sk_sp&& picture) {
- JNIEnv* env = getenv(globalCallbackRef->vm());
+ JNIEnv* env = GraphicsJNI::getJNIEnv();
Picture* wrapper = new PictureWrapper{std::move(picture), pictureState};
env->CallStaticVoidMethod(gHardwareRenderer.clazz,
gHardwareRenderer.invokePictureCapturedCallback,
@@ -600,7 +557,7 @@ static void android_view_ThreadedRenderer_setASurfaceTransactionCallback(
vm, env->NewGlobalRef(aSurfaceTransactionCallback));
proxy->setASurfaceTransactionCallback(
[globalCallbackRef](int64_t transObj, int64_t scObj, int64_t frameNr) -> bool {
- JNIEnv* env = getenv(globalCallbackRef->vm());
+ JNIEnv* env = GraphicsJNI::getJNIEnv();
jboolean ret = env->CallBooleanMethod(
globalCallbackRef->object(),
gASurfaceTransactionCallback.onMergeTransaction,
@@ -622,7 +579,7 @@ static void android_view_ThreadedRenderer_setPrepareSurfaceControlForWebviewCall
auto globalCallbackRef =
std::make_shared(vm, env->NewGlobalRef(callback));
proxy->setPrepareSurfaceControlForWebviewCallback([globalCallbackRef]() {
- JNIEnv* env = getenv(globalCallbackRef->vm());
+ JNIEnv* env = GraphicsJNI::getJNIEnv();
env->CallVoidMethod(globalCallbackRef->object(),
gPrepareSurfaceControlForWebviewCallback.prepare);
});
@@ -641,7 +598,7 @@ static void android_view_ThreadedRenderer_setFrameCallback(JNIEnv* env,
env->NewGlobalRef(frameCallback));
proxy->setFrameCallback([globalCallbackRef](int32_t syncResult,
int64_t frameNr) -> std::function {
- JNIEnv* env = getenv(globalCallbackRef->vm());
+ JNIEnv* env = GraphicsJNI::getJNIEnv();
ScopedLocalRef frameCommitCallback(
env, env->CallObjectMethod(
globalCallbackRef->object(), gFrameDrawingCallback.onFrameDraw,
@@ -680,7 +637,7 @@ static void android_view_ThreadedRenderer_setFrameCompleteCallback(JNIEnv* env,
auto globalCallbackRef =
std::make_shared(vm, env->NewGlobalRef(callback));
proxy->setFrameCompleteCallback([globalCallbackRef]() {
- JNIEnv* env = getenv(globalCallbackRef->vm());
+ JNIEnv* env = GraphicsJNI::getJNIEnv();
env->CallVoidMethod(globalCallbackRef->object(),
gFrameCompleteCallback.onFrameComplete);
});
@@ -693,7 +650,7 @@ public:
: CopyRequest(srcRect), mRefHolder(vm, jCopyRequest) {}
virtual SkBitmap getDestinationBitmap(int srcWidth, int srcHeight) override {
- JNIEnv* env = getenv(mRefHolder.vm());
+ JNIEnv* env = GraphicsJNI::getJNIEnv();
jlong bitmapPtr = env->CallLongMethod(
mRefHolder.object(), gCopyRequest.getDestinationBitmap, srcWidth, srcHeight);
SkBitmap bitmap;
@@ -702,7 +659,7 @@ public:
}
virtual void onCopyFinished(CopyResult result) override {
- JNIEnv* env = getenv(mRefHolder.vm());
+ JNIEnv* env = GraphicsJNI::getJNIEnv();
env->CallVoidMethod(mRefHolder.object(), gCopyRequest.onCopyFinished,
static_cast(result));
}
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index 19cd7bdd6fcb..202a62cf320c 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -55,7 +55,9 @@ SkiaOpenGLPipeline::~SkiaOpenGLPipeline() {
MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() {
// In case the surface was destroyed (e.g. a previous trimMemory call) we
// need to recreate it here.
- if (!isSurfaceReady() && mNativeWindow) {
+ if (mHardwareBuffer) {
+ mRenderThread.requireGlContext();
+ } else if (!isSurfaceReady() && mNativeWindow) {
setSurface(mNativeWindow.get(), mSwapBehavior);
}
@@ -67,17 +69,24 @@ MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() {
}
Frame SkiaOpenGLPipeline::getFrame() {
- LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE,
- "drawRenderNode called on a context with no surface!");
- return mEglManager.beginFrame(mEglSurface);
+ if (mHardwareBuffer) {
+ AHardwareBuffer_Desc description;
+ AHardwareBuffer_describe(mHardwareBuffer, &description);
+ return Frame(description.width, description.height, 0);
+ } else {
+ LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE,
+ "drawRenderNode called on a context with no surface!");
+ return mEglManager.beginFrame(mEglSurface);
+ }
}
IRenderPipeline::DrawResult SkiaOpenGLPipeline::draw(
const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
- const std::vector>& renderNodes, FrameInfoVisualizer* profiler) {
- if (!isCapturingSkp()) {
+ const std::vector>& renderNodes, FrameInfoVisualizer* profiler,
+ const HardwareBufferRenderParams& bufferParams) {
+ if (!isCapturingSkp() && !mHardwareBuffer) {
mEglManager.damageFrame(frame, dirty);
}
@@ -104,13 +113,25 @@ IRenderPipeline::DrawResult SkiaOpenGLPipeline::draw(
SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
SkASSERT(mRenderThread.getGrContext() != nullptr);
- sk_sp surface(SkSurface::MakeFromBackendRenderTarget(
- mRenderThread.getGrContext(), backendRT, this->getSurfaceOrigin(), colorType,
- mSurfaceColorSpace, &props));
+ sk_sp surface;
+ SkMatrix preTransform;
+ if (mHardwareBuffer) {
+ surface = getBufferSkSurface(bufferParams);
+ preTransform = bufferParams.getTransform();
+ } else {
+ surface = SkSurface::MakeFromBackendRenderTarget(mRenderThread.getGrContext(), backendRT,
+ getSurfaceOrigin(), colorType,
+ mSurfaceColorSpace, &props);
+ preTransform = SkMatrix::I();
+ }
- LightingInfo::updateLighting(lightGeometry, lightInfo);
+ SkPoint lightCenter = preTransform.mapXY(lightGeometry.center.x, lightGeometry.center.y);
+ LightGeometry localGeometry = lightGeometry;
+ localGeometry.center.x = lightCenter.fX;
+ localGeometry.center.y = lightCenter.fY;
+ LightingInfo::updateLighting(localGeometry, lightInfo);
renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface,
- SkMatrix::I());
+ preTransform);
// Draw visual debugging features
if (CC_UNLIKELY(Properties::showDirtyRegions ||
@@ -142,6 +163,10 @@ bool SkiaOpenGLPipeline::swapBuffers(const Frame& frame, bool drew, const SkRect
// metrics the frame was swapped at this point
currentFrameInfo->markSwapBuffers();
+ if (mHardwareBuffer) {
+ return false;
+ }
+
*requireSwap = drew || mEglManager.damageRequiresSwap();
if (*requireSwap && (CC_UNLIKELY(!mEglManager.swapBuffers(frame, screenDirty)))) {
@@ -197,6 +222,26 @@ bool SkiaOpenGLPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBeh
return false;
}
+[[nodiscard]] android::base::unique_fd SkiaOpenGLPipeline::flush() {
+ int fence = -1;
+ EGLSyncKHR sync = EGL_NO_SYNC_KHR;
+ mEglManager.createReleaseFence(true, &sync, &fence);
+ // If a sync object is returned here then the device does not support native
+ // fences, we block on the returned sync and return -1 as a file descriptor
+ if (sync != EGL_NO_SYNC_KHR) {
+ EGLDisplay display = mEglManager.eglDisplay();
+ EGLint result = eglClientWaitSyncKHR(display, sync, 0, 1000000000);
+ if (result == EGL_FALSE) {
+ ALOGE("EglManager::createReleaseFence: error waiting for previous fence: %#x",
+ eglGetError());
+ } else if (result == EGL_TIMEOUT_EXPIRED_KHR) {
+ ALOGE("EglManager::createReleaseFence: timeout waiting for previous fence");
+ }
+ eglDestroySyncKHR(display, sync);
+ }
+ return android::base::unique_fd(fence);
+}
+
bool SkiaOpenGLPipeline::isSurfaceReady() {
return CC_UNLIKELY(mEglSurface != EGL_NO_SURFACE);
}
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
index a80c613697f2..940d6bfdb83c 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
@@ -21,6 +21,7 @@
#include "SkiaPipeline.h"
#include "renderstate/RenderState.h"
+#include "renderthread/HardwareBufferRenderParams.h"
namespace android {
@@ -36,19 +37,18 @@ public:
renderthread::MakeCurrentResult makeCurrent() override;
renderthread::Frame getFrame() override;
- renderthread::IRenderPipeline::DrawResult draw(const renderthread::Frame& frame,
- const SkRect& screenDirty, const SkRect& dirty,
- const LightGeometry& lightGeometry,
- LayerUpdateQueue* layerUpdateQueue,
- const Rect& contentDrawBounds, bool opaque,
- const LightInfo& lightInfo,
- const std::vector >& renderNodes,
- FrameInfoVisualizer* profiler) override;
+ renderthread::IRenderPipeline::DrawResult draw(
+ const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
+ const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
+ const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
+ const std::vector >& renderNodes, FrameInfoVisualizer* profiler,
+ const renderthread::HardwareBufferRenderParams& bufferParams) override;
GrSurfaceOrigin getSurfaceOrigin() override { return kBottomLeft_GrSurfaceOrigin; }
bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty,
FrameInfo* currentFrameInfo, bool* requireSwap) override;
DeferredLayerUpdater* createTextureLayer() override;
bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior) override;
+ [[nodiscard]] android::base::unique_fd flush() override;
void onStop() override;
bool isSurfaceReady() override;
bool isContextReady() override;
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index c546adaaf779..1a336c5855d9 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -604,6 +604,31 @@ void SkiaPipeline::dumpResourceCacheUsage() const {
ALOGD("%s", log.c_str());
}
+void SkiaPipeline::setHardwareBuffer(AHardwareBuffer* buffer) {
+ if (mHardwareBuffer) {
+ AHardwareBuffer_release(mHardwareBuffer);
+ mHardwareBuffer = nullptr;
+ }
+
+ if (buffer) {
+ AHardwareBuffer_acquire(buffer);
+ mHardwareBuffer = buffer;
+ }
+}
+
+sk_sp SkiaPipeline::getBufferSkSurface(
+ const renderthread::HardwareBufferRenderParams& bufferParams) {
+ auto bufferColorSpace = bufferParams.getColorSpace();
+ if (mBufferSurface == nullptr || mBufferColorSpace == nullptr ||
+ !SkColorSpace::Equals(mBufferColorSpace.get(), bufferColorSpace.get())) {
+ mBufferSurface = SkSurface::MakeFromAHardwareBuffer(
+ mRenderThread.getGrContext(), mHardwareBuffer, kTopLeft_GrSurfaceOrigin,
+ bufferColorSpace, nullptr, true);
+ mBufferColorSpace = bufferColorSpace;
+ }
+ return mBufferSurface;
+}
+
void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) {
mColorMode = colorMode;
switch (colorMode) {
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index 7887d1ae2117..4f9334654c9b 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -20,9 +20,11 @@
#include
#include
#include
+
#include "Lighting.h"
#include "hwui/AnimatedImageDrawable.h"
#include "renderthread/CanvasContext.h"
+#include "renderthread/HardwareBufferRenderParams.h"
#include "renderthread/IRenderPipeline.h"
class SkFILEWStream;
@@ -73,11 +75,20 @@ public:
mCaptureMode = callback ? CaptureMode::CallbackAPI : CaptureMode::None;
}
+ virtual void setHardwareBuffer(AHardwareBuffer* buffer) override;
+ bool hasHardwareBuffer() override { return mHardwareBuffer != nullptr; }
+
protected:
+ sk_sp getBufferSkSurface(
+ const renderthread::HardwareBufferRenderParams& bufferParams);
void dumpResourceCacheUsage() const;
renderthread::RenderThread& mRenderThread;
+ AHardwareBuffer* mHardwareBuffer = nullptr;
+ sk_sp mBufferSurface = nullptr;
+ sk_sp mBufferColorSpace = nullptr;
+
ColorMode mColorMode = ColorMode::Default;
SkColorType mSurfaceColorType;
sk_sp mSurfaceColorSpace;
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index f10bca675bf7..b94b6cf0546a 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -57,37 +57,55 @@ VulkanManager& SkiaVulkanPipeline::vulkanManager() {
MakeCurrentResult SkiaVulkanPipeline::makeCurrent() {
// In case the surface was destroyed (e.g. a previous trimMemory call) we
// need to recreate it here.
- if (!isSurfaceReady() && mNativeWindow) {
+ if (mHardwareBuffer) {
+ mRenderThread.requireVkContext();
+ } else if (!isSurfaceReady() && mNativeWindow) {
setSurface(mNativeWindow.get(), SwapBehavior::kSwap_default);
}
return isContextReady() ? MakeCurrentResult::AlreadyCurrent : MakeCurrentResult::Failed;
}
Frame SkiaVulkanPipeline::getFrame() {
- LOG_ALWAYS_FATAL_IF(mVkSurface == nullptr, "getFrame() called on a context with no surface!");
- return vulkanManager().dequeueNextBuffer(mVkSurface);
+ if (mHardwareBuffer) {
+ AHardwareBuffer_Desc description;
+ AHardwareBuffer_describe(mHardwareBuffer, &description);
+ return Frame(description.width, description.height, 0);
+ } else {
+ LOG_ALWAYS_FATAL_IF(mVkSurface == nullptr,
+ "getFrame() called on a context with no surface!");
+ return vulkanManager().dequeueNextBuffer(mVkSurface);
+ }
}
IRenderPipeline::DrawResult SkiaVulkanPipeline::draw(
const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
- const std::vector>& renderNodes, FrameInfoVisualizer* profiler) {
- sk_sp backBuffer = mVkSurface->getCurrentSkSurface();
+ const std::vector>& renderNodes, FrameInfoVisualizer* profiler,
+ const HardwareBufferRenderParams& bufferParams) {
+ sk_sp backBuffer;
+ SkMatrix preTransform;
+ if (mHardwareBuffer) {
+ backBuffer = getBufferSkSurface(bufferParams);
+ preTransform = bufferParams.getTransform();
+ } else {
+ backBuffer = mVkSurface->getCurrentSkSurface();
+ preTransform = mVkSurface->getCurrentPreTransform();
+ }
+
if (backBuffer.get() == nullptr) {
return {false, -1};
}
// update the coordinates of the global light position based on surface rotation
- SkPoint lightCenter = mVkSurface->getCurrentPreTransform().mapXY(lightGeometry.center.x,
- lightGeometry.center.y);
+ SkPoint lightCenter = preTransform.mapXY(lightGeometry.center.x, lightGeometry.center.y);
LightGeometry localGeometry = lightGeometry;
localGeometry.center.x = lightCenter.fX;
localGeometry.center.y = lightCenter.fY;
LightingInfo::updateLighting(localGeometry, lightInfo);
renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, backBuffer,
- mVkSurface->getCurrentPreTransform());
+ preTransform);
// Draw visual debugging features
if (CC_UNLIKELY(Properties::showDirtyRegions ||
@@ -116,12 +134,16 @@ IRenderPipeline::DrawResult SkiaVulkanPipeline::draw(
bool SkiaVulkanPipeline::swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty,
FrameInfo* currentFrameInfo, bool* requireSwap) {
- *requireSwap = drew;
-
// Even if we decided to cancel the frame, from the perspective of jank
// metrics the frame was swapped at this point
currentFrameInfo->markSwapBuffers();
+ if (mHardwareBuffer) {
+ return false;
+ }
+
+ *requireSwap = drew;
+
if (*requireSwap) {
vulkanManager().swapBuffers(mVkSurface, screenDirty);
}
@@ -137,6 +159,12 @@ DeferredLayerUpdater* SkiaVulkanPipeline::createTextureLayer() {
void SkiaVulkanPipeline::onStop() {}
+[[nodiscard]] android::base::unique_fd SkiaVulkanPipeline::flush() {
+ int fence = -1;
+ vulkanManager().createReleaseFence(&fence, mRenderThread.getGrContext());
+ return android::base::unique_fd(fence);
+}
+
// We can safely ignore the swap behavior because VkManager will always operate
// in a mode equivalent to EGLManager::SwapBehavior::kBufferAge
bool SkiaVulkanPipeline::setSurface(ANativeWindow* surface, SwapBehavior /*swapBehavior*/) {
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
index f3d36131a22f..2c7b268e8174 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
@@ -16,14 +16,13 @@
#pragma once
+#include "SkRefCnt.h"
#include "SkiaPipeline.h"
+#include "renderstate/RenderState.h"
+#include "renderthread/HardwareBufferRenderParams.h"
#include "renderthread/VulkanManager.h"
#include "renderthread/VulkanSurface.h"
-#include "renderstate/RenderState.h"
-
-#include "SkRefCnt.h"
-
class SkBitmap;
struct SkRect;
@@ -38,18 +37,18 @@ public:
renderthread::MakeCurrentResult makeCurrent() override;
renderthread::Frame getFrame() override;
- renderthread::IRenderPipeline::DrawResult draw(const renderthread::Frame& frame,
- const SkRect& screenDirty, const SkRect& dirty,
- const LightGeometry& lightGeometry,
- LayerUpdateQueue* layerUpdateQueue,
- const Rect& contentDrawBounds, bool opaque,
- const LightInfo& lightInfo,
- const std::vector >& renderNodes,
- FrameInfoVisualizer* profiler) override;
+ renderthread::IRenderPipeline::DrawResult draw(
+ const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
+ const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
+ const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
+ const std::vector >& renderNodes, FrameInfoVisualizer* profiler,
+ const renderthread::HardwareBufferRenderParams& bufferParams) override;
GrSurfaceOrigin getSurfaceOrigin() override { return kTopLeft_GrSurfaceOrigin; }
bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty,
FrameInfo* currentFrameInfo, bool* requireSwap) override;
DeferredLayerUpdater* createTextureLayer() override;
+ [[nodiscard]] android::base::unique_fd flush() override;
+
bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior) override;
void onStop() override;
bool isSurfaceReady() override;
@@ -64,7 +63,6 @@ protected:
private:
renderthread::VulkanManager& vulkanManager();
-
renderthread::VulkanSurface* mVkSurface = nullptr;
sp mNativeWindow;
};
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 78ae5cf3d93b..b769f8d15044 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -153,6 +153,7 @@ void CanvasContext::removeRenderNode(RenderNode* node) {
void CanvasContext::destroy() {
stopDrawing();
+ setHardwareBuffer(nullptr);
setSurface(nullptr);
setSurfaceControl(nullptr);
freePrefetchedLayers();
@@ -176,6 +177,19 @@ static void setBufferCount(ANativeWindow* window) {
native_window_set_buffer_count(window, bufferCount);
}
+void CanvasContext::setHardwareBuffer(AHardwareBuffer* buffer) {
+ if (mHardwareBuffer) {
+ AHardwareBuffer_release(mHardwareBuffer);
+ mHardwareBuffer = nullptr;
+ }
+
+ if (buffer) {
+ AHardwareBuffer_acquire(buffer);
+ mHardwareBuffer = buffer;
+ }
+ mRenderPipeline->setHardwareBuffer(mHardwareBuffer);
+}
+
void CanvasContext::setSurface(ANativeWindow* window, bool enableTimeout) {
ATRACE_CALL();
@@ -257,7 +271,7 @@ void CanvasContext::setStopped(bool stopped) {
mRenderThread.removeFrameCallback(this);
mRenderPipeline->onStop();
mRenderThread.cacheManager().onContextStopped(this);
- } else if (mIsDirty && hasSurface()) {
+ } else if (mIsDirty && hasOutputTarget()) {
mRenderThread.postFrameCallback(this);
}
}
@@ -390,7 +404,7 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy
mIsDirty = true;
- if (CC_UNLIKELY(!hasSurface())) {
+ if (CC_UNLIKELY(!hasOutputTarget())) {
mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
info.out.canDrawThisFrame = false;
return;
@@ -535,7 +549,7 @@ void CanvasContext::draw() {
std::scoped_lock lock(mFrameMetricsReporterMutex);
drawResult = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry,
&mLayerUpdateQueue, mContentDrawBounds, mOpaque,
- mLightInfo, mRenderNodes, &(profiler()));
+ mLightInfo, mRenderNodes, &(profiler()), mBufferParams);
}
uint64_t frameCompleteNr = getFrameNumber();
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index e875c42e9eba..3f796d9b7b65 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -125,12 +125,13 @@ public:
// Won't take effect until next EGLSurface creation
void setSwapBehavior(SwapBehavior swapBehavior);
+ void setHardwareBuffer(AHardwareBuffer* buffer);
void setSurface(ANativeWindow* window, bool enableTimeout = true);
void setSurfaceControl(ASurfaceControl* surfaceControl);
bool pauseSurface();
void setStopped(bool stopped);
- bool isStopped() { return mStopped || !hasSurface(); }
- bool hasSurface() const { return mNativeSurface.get(); }
+ bool isStopped() { return mStopped || !hasOutputTarget(); }
+ bool hasOutputTarget() const { return mNativeSurface.get() || mHardwareBuffer; }
void allocateBuffers();
void setLightAlpha(uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha);
@@ -206,6 +207,10 @@ public:
mASurfaceTransactionCallback = callback;
}
+ void setHardwareBufferRenderParams(const HardwareBufferRenderParams& params) {
+ mBufferParams = params;
+ }
+
bool mergeTransaction(ASurfaceTransaction* transaction, ASurfaceControl* control);
void setPrepareSurfaceControlForWebviewCallback(const std::function& callback) {
@@ -258,6 +263,9 @@ private:
int32_t mLastFrameHeight = 0;
RenderThread& mRenderThread;
+
+ AHardwareBuffer* mHardwareBuffer = nullptr;
+ HardwareBufferRenderParams mBufferParams;
std::unique_ptr mNativeSurface;
// The SurfaceControl reference is passed from ViewRootImpl, can be set to
// NULL to remove the reference
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index 1cc82fd0ff64..b06c5dd9ad36 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -26,6 +26,7 @@
#include "../Properties.h"
#include "../RenderNode.h"
#include "CanvasContext.h"
+#include "HardwareBufferRenderParams.h"
#include "RenderThread.h"
namespace android {
@@ -91,6 +92,9 @@ void DrawFrameTask::run() {
mContext->setSyncDelayDuration(systemTime(SYSTEM_TIME_MONOTONIC) - mSyncQueued);
+ auto hardwareBufferParams = mHardwareBufferParams;
+ mContext->setHardwareBufferRenderParams(hardwareBufferParams);
+ IRenderPipeline* pipeline = mContext->getRenderPipeline();
bool canUnblockUiThread;
bool canDrawThisFrame;
{
@@ -150,6 +154,11 @@ void DrawFrameTask::run() {
if (!canUnblockUiThread) {
unblockUiThread();
}
+
+ if (pipeline->hasHardwareBuffer()) {
+ auto fence = pipeline->flush();
+ hardwareBufferParams.invokeRenderCallback(std::move(fence), 0);
+ }
}
bool DrawFrameTask::syncFrameState(TreeInfo& info) {
@@ -175,8 +184,9 @@ bool DrawFrameTask::syncFrameState(TreeInfo& info) {
// This is after the prepareTree so that any pending operations
// (RenderNode tree state, prefetched layers, etc...) will be flushed.
- if (CC_UNLIKELY(!mContext->hasSurface() || !canDraw)) {
- if (!mContext->hasSurface()) {
+ bool hasTarget = mContext->hasOutputTarget();
+ if (CC_UNLIKELY(!hasTarget || !canDraw)) {
+ if (!hasTarget) {
mSyncResult |= SyncResult::LostSurfaceRewardIfFound;
} else {
// If we have a surface but can't draw we must be stopped
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index fafab24cbce7..c5c5fe280743 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -27,8 +27,16 @@
#include "../Rect.h"
#include "../TreeInfo.h"
#include "RenderTask.h"
+#include "SkColorSpace.h"
+#include "SwapBehavior.h"
+#include "utils/TimeUtils.h"
+#ifdef __ANDROID__ // Layoutlib does not support hardware acceleration
+#include
+#endif
+#include "HardwareBufferRenderParams.h"
namespace android {
+
namespace uirenderer {
class DeferredLayerUpdater;
@@ -88,6 +96,10 @@ public:
void forceDrawNextFrame() { mForceDrawFrame = true; }
+ void setHardwareBufferRenderParams(const HardwareBufferRenderParams& params) {
+ mHardwareBufferParams = params;
+ }
+
private:
void postAndWait();
bool syncFrameState(TreeInfo& info);
@@ -111,6 +123,7 @@ private:
int64_t mFrameInfo[UI_THREAD_FRAME_INFO_SIZE];
+ HardwareBufferRenderParams mHardwareBufferParams;
std::function(int32_t, int64_t)> mFrameCallback;
std::function mFrameCommitCallback;
std::function mFrameCompleteCallback;
diff --git a/libs/hwui/renderthread/HardwareBufferRenderParams.h b/libs/hwui/renderthread/HardwareBufferRenderParams.h
new file mode 100644
index 000000000000..91fe3f6cf273
--- /dev/null
+++ b/libs/hwui/renderthread/HardwareBufferRenderParams.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+#ifndef HARDWAREBUFFERRENDERER_H_
+#define HARDWAREBUFFERRENDERER_H_
+
+#include
+#include
+
+#include "SkColorSpace.h"
+#include "SkMatrix.h"
+#include "SkSurface.h"
+
+namespace android {
+namespace uirenderer {
+namespace renderthread {
+
+using namespace android::uirenderer::renderthread;
+
+using RenderCallback = std::function;
+
+class RenderProxy;
+
+class HardwareBufferRenderParams {
+public:
+ HardwareBufferRenderParams() = default;
+ HardwareBufferRenderParams(const SkMatrix& transform, const sk_sp& colorSpace,
+ RenderCallback&& callback)
+ : mTransform(transform)
+ , mColorSpace(colorSpace)
+ , mRenderCallback(std::move(callback)) {}
+ const SkMatrix& getTransform() const { return mTransform; }
+ sk_sp getColorSpace() const { return mColorSpace; }
+
+ void invokeRenderCallback(android::base::unique_fd&& fenceFd, int status) {
+ if (mRenderCallback) {
+ std::invoke(mRenderCallback, std::move(fenceFd), status);
+ }
+ }
+
+private:
+ SkMatrix mTransform = SkMatrix::I();
+ sk_sp mColorSpace = SkColorSpace::MakeSRGB();
+ RenderCallback mRenderCallback = nullptr;
+};
+
+} // namespace renderthread
+} // namespace uirenderer
+} // namespace android
+#endif // HARDWAREBUFFERRENDERER_H_
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index 35e370f52fda..715c17dfc895 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -16,17 +16,19 @@
#pragma once
+#include
+#include
+#include
+#include
+
+#include "ColorMode.h"
#include "DamageAccumulator.h"
#include "FrameInfoVisualizer.h"
+#include "HardwareBufferRenderParams.h"
#include "LayerUpdateQueue.h"
#include "Lighting.h"
#include "SwapBehavior.h"
#include "hwui/Bitmap.h"
-#include "ColorMode.h"
-
-#include
-#include
-#include
class GrDirectContext;
@@ -64,10 +66,14 @@ public:
const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
const std::vector>& renderNodes,
- FrameInfoVisualizer* profiler) = 0;
+ FrameInfoVisualizer* profiler,
+ const HardwareBufferRenderParams& bufferParams) = 0;
virtual bool swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty,
FrameInfo* currentFrameInfo, bool* requireSwap) = 0;
virtual DeferredLayerUpdater* createTextureLayer() = 0;
+ [[nodiscard]] virtual android::base::unique_fd flush() = 0;
+ virtual void setHardwareBuffer(AHardwareBuffer* hardwareBuffer) = 0;
+ virtual bool hasHardwareBuffer() = 0;
virtual bool setSurface(ANativeWindow* window, SwapBehavior swapBehavior) = 0;
virtual void onStop() = 0;
virtual bool isSurfaceReady() = 0;
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 07f5a78fc1ec..ed01e322ffd9 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -85,6 +85,18 @@ void RenderProxy::setName(const char* name) {
mRenderThread.queue().runSync([this, name]() { mContext->setName(std::string(name)); });
}
+void RenderProxy::setHardwareBuffer(AHardwareBuffer* buffer) {
+ if (buffer) {
+ AHardwareBuffer_acquire(buffer);
+ }
+ mRenderThread.queue().post([this, hardwareBuffer = buffer]() mutable {
+ mContext->setHardwareBuffer(hardwareBuffer);
+ if (hardwareBuffer) {
+ AHardwareBuffer_release(hardwareBuffer);
+ }
+ });
+}
+
void RenderProxy::setSurface(ANativeWindow* window, bool enableTimeout) {
if (window) { ANativeWindow_acquire(window); }
mRenderThread.queue().post([this, win = window, enableTimeout]() mutable {
@@ -324,6 +336,10 @@ void RenderProxy::setContentDrawBounds(int left, int top, int right, int bottom)
mDrawFrameTask.setContentDrawBounds(left, top, right, bottom);
}
+void RenderProxy::setHardwareBufferRenderParams(const HardwareBufferRenderParams& params) {
+ mDrawFrameTask.setHardwareBufferRenderParams(params);
+}
+
void RenderProxy::setPictureCapturedCallback(
const std::function&&)>& callback) {
mRenderThread.queue().post(
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index a21faa86ed0f..17cf6650f87d 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -18,6 +18,7 @@
#define RENDERPROXY_H_
#include
+#include
#include
#include
#include
@@ -76,7 +77,7 @@ public:
void setSwapBehavior(SwapBehavior swapBehavior);
bool loadSystemProperties();
void setName(const char* name);
-
+ void setHardwareBuffer(AHardwareBuffer* buffer);
void setSurface(ANativeWindow* window, bool enableTimeout = true);
void setSurfaceControl(ASurfaceControl* surfaceControl);
void allocateBuffers();
@@ -84,6 +85,7 @@ public:
void setStopped(bool stopped);
void setLightAlpha(uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha);
void setLightGeometry(const Vector3& lightCenter, float lightRadius);
+ void setHardwareBufferRenderParams(const HardwareBufferRenderParams& params);
void setOpaque(bool opaque);
void setColorMode(ColorMode mode);
int64_t* frameInfo();
diff --git a/libs/hwui/tests/unit/CanvasContextTests.cpp b/libs/hwui/tests/unit/CanvasContextTests.cpp
index 88420a5d5c23..9e376e32f8ea 100644
--- a/libs/hwui/tests/unit/CanvasContextTests.cpp
+++ b/libs/hwui/tests/unit/CanvasContextTests.cpp
@@ -38,7 +38,7 @@ RENDERTHREAD_TEST(CanvasContext, create) {
std::unique_ptr canvasContext(
CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory, 0, 0));
- ASSERT_FALSE(canvasContext->hasSurface());
+ ASSERT_FALSE(canvasContext->hasOutputTarget());
canvasContext->destroy();
}
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 7383d6ab4994..616f21cbe1a4 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -1136,6 +1136,15 @@
+
+
+
+
+
+
+
{
+ result.getFence().await(Duration.ofMillis(3000));
+ handler.post(() -> {
+ Bitmap bitmap = Bitmap.wrapHardwareBuffer(buffer, colorSpace);
+ Bitmap copy = bitmap.copy(Config.ARGB_8888, false);
+ mImageView.setImageBitmap(copy);
+ });
+ });
+ }
+}
--
cgit v1.2.3-59-g8ed1b
From 2e58b5c4b9f7d96ce84ccc0dabe2fcd3667052fe Mon Sep 17 00:00:00 2001
From: Jorge Betancourt
Date: Thu, 1 Sep 2022 09:57:09 -0400
Subject: add support for rendering lottie animations through a LottieDrawable
This is an initial push that only supports basic playback
Test: frameworks/base/tests/VectorDrawableTest and run LottieDrawable activity
Change-Id: Ic34366b0cd0984a512d8684d476227830903f778
Bug: 257304231
---
.../android/graphics/drawable/LottieDrawable.java | 151 +++++++++++++++++++++
libs/hwui/Android.bp | 4 +
libs/hwui/SkiaCanvas.cpp | 4 +
libs/hwui/SkiaCanvas.h | 1 +
libs/hwui/apex/jni_runtime.cpp | 2 +
libs/hwui/hwui/Canvas.h | 2 +
libs/hwui/hwui/LottieDrawable.cpp | 83 +++++++++++
libs/hwui/hwui/LottieDrawable.h | 67 +++++++++
libs/hwui/jni/LottieDrawable.cpp | 91 +++++++++++++
libs/hwui/pipeline/skia/SkiaDisplayList.cpp | 10 ++
libs/hwui/pipeline/skia/SkiaDisplayList.h | 3 +
libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp | 5 +
libs/hwui/pipeline/skia/SkiaRecordingCanvas.h | 1 +
tests/VectorDrawableTest/Android.bp | 2 +
tests/VectorDrawableTest/AndroidManifest.xml | 9 ++
tests/VectorDrawableTest/res/raw/lottie.json | 123 +++++++++++++++++
.../android/test/dynamic/LottieDrawableTest.java | 76 +++++++++++
17 files changed, 634 insertions(+)
create mode 100644 graphics/java/android/graphics/drawable/LottieDrawable.java
create mode 100644 libs/hwui/hwui/LottieDrawable.cpp
create mode 100644 libs/hwui/hwui/LottieDrawable.h
create mode 100644 libs/hwui/jni/LottieDrawable.cpp
create mode 100644 tests/VectorDrawableTest/res/raw/lottie.json
create mode 100644 tests/VectorDrawableTest/src/com/android/test/dynamic/LottieDrawableTest.java
(limited to 'graphics/java')
diff --git a/graphics/java/android/graphics/drawable/LottieDrawable.java b/graphics/java/android/graphics/drawable/LottieDrawable.java
new file mode 100644
index 000000000000..c1f1b50cf339
--- /dev/null
+++ b/graphics/java/android/graphics/drawable/LottieDrawable.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2022 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.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.PixelFormat;
+
+import dalvik.annotation.optimization.FastNative;
+
+import libcore.util.NativeAllocationRegistry;
+
+import java.io.IOException;
+
+/**
+ * {@link Drawable} for drawing Lottie files.
+ *
+ * The framework handles decoding subsequent frames in another thread and
+ * updating when necessary. The drawable will only animate while it is being
+ * displayed.
+ *
+ * @hide
+ */
+@SuppressLint("NotCloseable")
+public class LottieDrawable extends Drawable implements Animatable {
+ private long mNativePtr;
+
+ /**
+ * Create an animation from the provided JSON string
+ * @hide
+ */
+ private LottieDrawable(@NonNull String animationJson) throws IOException {
+ mNativePtr = nCreate(animationJson);
+ if (mNativePtr == 0) {
+ throw new IOException("could not make LottieDrawable from json");
+ }
+
+ final long nativeSize = nNativeByteSize(mNativePtr);
+ NativeAllocationRegistry registry = new NativeAllocationRegistry(
+ LottieDrawable.class.getClassLoader(), nGetNativeFinalizer(), nativeSize);
+ registry.registerNativeAllocation(this, mNativePtr);
+ }
+
+ /**
+ * Factory for LottieDrawable, throws IOException if native Skottie Builder fails to parse
+ */
+ public static LottieDrawable makeLottieDrawable(@NonNull String animationJson)
+ throws IOException {
+ return new LottieDrawable(animationJson);
+ }
+
+
+
+ /**
+ * Draw the current frame to the Canvas.
+ * @hide
+ */
+ @Override
+ public void draw(@NonNull Canvas canvas) {
+ if (mNativePtr == 0) {
+ throw new IllegalStateException("called draw on empty LottieDrawable");
+ }
+
+ nDraw(mNativePtr, canvas.getNativeCanvasWrapper());
+ }
+
+ /**
+ * Start the animation. Needs to be called before draw calls.
+ * @hide
+ */
+ @Override
+ public void start() {
+ if (mNativePtr == 0) {
+ throw new IllegalStateException("called start on empty LottieDrawable");
+ }
+
+ if (nStart(mNativePtr)) {
+ invalidateSelf();
+ }
+ }
+
+ /**
+ * Stops the animation playback. Does not release anything.
+ * @hide
+ */
+ @Override
+ public void stop() {
+ if (mNativePtr == 0) {
+ throw new IllegalStateException("called stop on empty LottieDrawable");
+ }
+ nStop(mNativePtr);
+ }
+
+ /**
+ * Return whether the animation is currently running.
+ */
+ @Override
+ public boolean isRunning() {
+ if (mNativePtr == 0) {
+ throw new IllegalStateException("called isRunning on empty LottieDrawable");
+ }
+ return nIsRunning(mNativePtr);
+ }
+
+ @Override
+ public int getOpacity() {
+ // We assume translucency to avoid checking each pixel.
+ return PixelFormat.TRANSLUCENT;
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ //TODO
+ }
+
+ @Override
+ public void setColorFilter(@Nullable ColorFilter colorFilter) {
+ //TODO
+ }
+
+ private static native long nCreate(String json);
+ private static native void nDraw(long nativeInstance, long nativeCanvas);
+ @FastNative
+ private static native long nGetNativeFinalizer();
+ @FastNative
+ private static native long nNativeByteSize(long nativeInstance);
+ @FastNative
+ private static native boolean nIsRunning(long nativeInstance);
+ @FastNative
+ private static native boolean nStart(long nativeInstance);
+ @FastNative
+ private static native boolean nStop(long nativeInstance);
+
+}
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 3e3d77b89a9a..9c4ea0ecbe30 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -78,6 +78,7 @@ cc_defaults {
"external/skia/src/utils",
"external/skia/src/gpu",
"external/skia/src/shaders",
+ "external/skia/modules/skottie",
],
},
host: {
@@ -374,6 +375,7 @@ cc_defaults {
"external/skia/src/effects",
"external/skia/src/image",
"external/skia/src/images",
+ "external/skia/modules/skottie",
],
shared_libs: [
@@ -400,6 +402,7 @@ cc_defaults {
"jni/BitmapRegionDecoder.cpp",
"jni/GIFMovie.cpp",
"jni/GraphicsStatsService.cpp",
+ "jni/LottieDrawable.cpp",
"jni/Movie.cpp",
"jni/MovieImpl.cpp",
"jni/pdf/PdfDocument.cpp",
@@ -507,6 +510,7 @@ cc_defaults {
"hwui/BlurDrawLooper.cpp",
"hwui/Canvas.cpp",
"hwui/ImageDecoder.cpp",
+ "hwui/LottieDrawable.cpp",
"hwui/MinikinSkia.cpp",
"hwui/MinikinUtils.cpp",
"hwui/PaintImpl.cpp",
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index d83d78f650aa..a1c4b49b6742 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -736,6 +736,10 @@ double SkiaCanvas::drawAnimatedImage(AnimatedImageDrawable* imgDrawable) {
return imgDrawable->drawStaging(mCanvas);
}
+void SkiaCanvas::drawLottie(LottieDrawable* lottieDrawable) {
+ lottieDrawable->drawStaging(mCanvas);
+}
+
void SkiaCanvas::drawVectorDrawable(VectorDrawableRoot* vectorDrawable) {
vectorDrawable->drawStaging(this);
}
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index 31e3b4c3c7e2..fd8b6cdb7ca0 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -145,6 +145,7 @@ public:
float dstTop, float dstRight, float dstBottom,
const Paint* paint) override;
virtual double drawAnimatedImage(AnimatedImageDrawable* imgDrawable) override;
+ virtual void drawLottie(LottieDrawable* lottieDrawable) override;
virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override;
diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp
index e6cfa7bcaf70..5a96174ebac0 100644
--- a/libs/hwui/apex/jni_runtime.cpp
+++ b/libs/hwui/apex/jni_runtime.cpp
@@ -37,6 +37,7 @@ extern int register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv* env);
extern int register_android_graphics_Graphics(JNIEnv* env);
extern int register_android_graphics_ImageDecoder(JNIEnv*);
extern int register_android_graphics_drawable_AnimatedImageDrawable(JNIEnv*);
+extern int register_android_graphics_drawable_LottieDrawable(JNIEnv*);
extern int register_android_graphics_Interpolator(JNIEnv* env);
extern int register_android_graphics_MaskFilter(JNIEnv* env);
extern int register_android_graphics_Movie(JNIEnv* env);
@@ -116,6 +117,7 @@ extern int register_android_view_ThreadedRenderer(JNIEnv* env);
REG_JNI(register_android_graphics_HardwareRendererObserver),
REG_JNI(register_android_graphics_ImageDecoder),
REG_JNI(register_android_graphics_drawable_AnimatedImageDrawable),
+ REG_JNI(register_android_graphics_drawable_LottieDrawable),
REG_JNI(register_android_graphics_Interpolator),
REG_JNI(register_android_graphics_MaskFilter),
REG_JNI(register_android_graphics_Matrix),
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 2a2019199bda..07e2fe24c939 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -60,6 +60,7 @@ typedef uirenderer::VectorDrawable::Tree VectorDrawableRoot;
typedef std::function ReadGlyphFunc;
class AnimatedImageDrawable;
+class LottieDrawable;
class Bitmap;
class Paint;
struct Typeface;
@@ -242,6 +243,7 @@ public:
const Paint* paint) = 0;
virtual double drawAnimatedImage(AnimatedImageDrawable* imgDrawable) = 0;
+ virtual void drawLottie(LottieDrawable* lottieDrawable) = 0;
virtual void drawPicture(const SkPicture& picture) = 0;
/**
diff --git a/libs/hwui/hwui/LottieDrawable.cpp b/libs/hwui/hwui/LottieDrawable.cpp
new file mode 100644
index 000000000000..92dc51e01a85
--- /dev/null
+++ b/libs/hwui/hwui/LottieDrawable.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include "LottieDrawable.h"
+
+#include
+#include
+#include
+
+namespace android {
+
+sk_sp LottieDrawable::Make(sk_sp animation, size_t bytesUsed) {
+ if (animation) {
+ return sk_sp(new LottieDrawable(std::move(animation), bytesUsed));
+ }
+ return nullptr;
+}
+LottieDrawable::LottieDrawable(sk_sp animation, size_t bytesUsed)
+ : mAnimation(std::move(animation)), mBytesUsed(bytesUsed) {}
+
+bool LottieDrawable::start() {
+ if (mRunning) {
+ return false;
+ }
+
+ mRunning = true;
+ return true;
+}
+
+bool LottieDrawable::stop() {
+ bool wasRunning = mRunning;
+ mRunning = false;
+ return wasRunning;
+}
+
+bool LottieDrawable::isRunning() {
+ return mRunning;
+}
+
+// TODO: Check to see if drawable is actually dirty
+bool LottieDrawable::isDirty() {
+ return true;
+}
+
+void LottieDrawable::onDraw(SkCanvas* canvas) {
+ if (mRunning) {
+ const nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+ nsecs_t t = 0;
+ if (mStartTime == 0) {
+ mStartTime = currentTime;
+ } else {
+ t = currentTime - mStartTime;
+ }
+ double seekTime = std::fmod((double)t * 1e-9, mAnimation->duration());
+ mAnimation->seekFrameTime(seekTime);
+ mAnimation->render(canvas);
+ }
+}
+
+void LottieDrawable::drawStaging(SkCanvas* canvas) {
+ onDraw(canvas);
+}
+
+SkRect LottieDrawable::onGetBounds() {
+ // We do not actually know the bounds, so give a conservative answer.
+ return SkRectMakeLargest();
+}
+
+} // namespace android
diff --git a/libs/hwui/hwui/LottieDrawable.h b/libs/hwui/hwui/LottieDrawable.h
new file mode 100644
index 000000000000..9cc34bf12f4f
--- /dev/null
+++ b/libs/hwui/hwui/LottieDrawable.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#pragma once
+
+#include
+#include
+#include
+
+class SkCanvas;
+
+namespace android {
+
+/**
+ * Native component of android.graphics.drawable.LottieDrawable.java.
+ * This class can be drawn into Canvas.h and maintains the state needed to drive
+ * the animation from the RenderThread.
+ */
+class LottieDrawable : public SkDrawable {
+public:
+ static sk_sp Make(sk_sp animation, size_t bytes);
+
+ // Draw to software canvas
+ void drawStaging(SkCanvas* canvas);
+
+ // Returns true if the animation was started; false otherwise (e.g. it was
+ // already running)
+ bool start();
+ // Returns true if the animation was stopped; false otherwise (e.g. it was
+ // already stopped)
+ bool stop();
+ bool isRunning();
+
+ // TODO: Is dirty should take in a time til next frame to determine if it is dirty
+ bool isDirty();
+
+ SkRect onGetBounds() override;
+
+ size_t byteSize() const { return sizeof(*this) + mBytesUsed; }
+
+protected:
+ void onDraw(SkCanvas* canvas) override;
+
+private:
+ LottieDrawable(sk_sp animation, size_t bytes_used);
+
+ sk_sp mAnimation;
+ bool mRunning = false;
+ // The start time for the drawable itself.
+ nsecs_t mStartTime = 0;
+ const size_t mBytesUsed = 0;
+};
+
+} // namespace android
diff --git a/libs/hwui/jni/LottieDrawable.cpp b/libs/hwui/jni/LottieDrawable.cpp
new file mode 100644
index 000000000000..fb6eede213a8
--- /dev/null
+++ b/libs/hwui/jni/LottieDrawable.cpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include
+#include
+#include
+#include
+
+#include "GraphicsJNI.h"
+#include "Utils.h"
+
+using namespace android;
+
+static jclass gLottieDrawableClass;
+
+static jlong LottieDrawable_nCreate(JNIEnv* env, jobject, jstring jjson) {
+ const ScopedUtfChars cstr(env, jjson);
+ // TODO(b/259267150) provide more accurate byteSize
+ size_t bytes = strlen(cstr.c_str());
+ auto animation = skottie::Animation::Builder().make(cstr.c_str(), bytes);
+ sk_sp drawable(LottieDrawable::Make(std::move(animation), bytes));
+ if (!drawable) {
+ return 0;
+ }
+ return reinterpret_cast(drawable.release());
+}
+
+static void LottieDrawable_destruct(LottieDrawable* drawable) {
+ SkSafeUnref(drawable);
+}
+
+static jlong LottieDrawable_nGetNativeFinalizer(JNIEnv* /*env*/, jobject /*clazz*/) {
+ return static_cast(reinterpret_cast(&LottieDrawable_destruct));
+}
+
+static void LottieDrawable_nDraw(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, jlong canvasPtr) {
+ auto* drawable = reinterpret_cast(nativePtr);
+ auto* canvas = reinterpret_cast
+
+
+
+
+
+
+
diff --git a/tests/VectorDrawableTest/res/raw/lottie.json b/tests/VectorDrawableTest/res/raw/lottie.json
new file mode 100644
index 000000000000..fea571c6bedb
--- /dev/null
+++ b/tests/VectorDrawableTest/res/raw/lottie.json
@@ -0,0 +1,123 @@
+{
+ "v":"4.6.9",
+ "fr":60,
+ "ip":0,
+ "op":200,
+ "w":800,
+ "h":600,
+ "nm":"Loader 1 JSON",
+ "ddd":0,
+
+
+ "layers":[
+ {
+ "ddd":0,
+ "ind":1,
+ "ty":4,
+ "nm":"Custom Path 1",
+ "ao": 0,
+ "ip": 0,
+ "op": 300,
+ "st": 0,
+ "sr": 1,
+ "bm": 0,
+ "ks": {
+ "o": { "a":0, "k":100 },
+ "r": { "a":1, "k": [
+ { "s": [ 0 ], "e": [ 360], "i": { "x":0.5, "y":0.5 }, "o": { "x":0.5, "y":0.5 }, "t": 0 },
+ { "t": 200 }
+ ] },
+ "p": { "a":0, "k":[ 300, 300, 0 ] },
+ "a": { "a":0, "k":[ 100, 100, 0 ] },
+ "s": { "a":1, "k":[
+ { "s": [ 100, 100 ], "e": [ 200, 200 ], "i": { "x":0.5, "y":0.5 }, "o": { "x":0.5, "y":0.5 }, "t": 0 },
+ { "s": [ 200, 200 ], "e": [ 100, 100 ], "i": { "x":0.5, "y":0.5 }, "o": { "x":0.5, "y":0.5 }, "t": 100 },
+ { "t": 200 }
+ ] }
+ },
+
+ "shapes":[
+ {
+ "ty":"gr",
+ "it":[
+ {
+ "ty" : "sh",
+ "nm" : "Path 1",
+ "ks" : {
+ "a" : 1,
+ "k" : [
+ {
+ "s": [ {
+ "i": [ [ 0, 50 ], [ -50, 0 ], [ 0, -50 ], [ 50, 0 ] ],
+ "o": [ [ 0, -50 ], [ 50, 0 ], [ 0, 50 ], [ -50, 0 ] ],
+ "v": [ [ 0, 100 ], [ 100, 0 ], [ 200, 100 ], [ 100, 200 ] ],
+ "c": true
+ } ],
+ "e": [ {
+ "i": [ [ 50, 50 ], [ -50, 0 ], [ -50, -50 ], [ 50, 50 ] ],
+ "o": [ [ 50, -50 ], [ 50, 0 ], [ -50, 50 ], [ -50, 50 ] ],
+ "v": [ [ 0, 100 ], [ 100, 0 ], [ 200, 100 ], [ 100, 200 ] ],
+ "c": true
+ } ],
+ "i": { "x":0.5, "y":0.5 },
+ "o": { "x":0.5, "y":0.5 },
+ "t": 0
+ },
+ {
+ "s": [ {
+ "i": [ [ 50, 50 ], [ -50, 0 ], [ -50, -50 ], [ 50, 50 ] ],
+ "o": [ [ 50, -50 ], [ 50, 0 ], [ -50, 50 ], [ -50, 50 ] ],
+ "v": [ [ 0, 100 ], [ 100, 0 ], [ 200, 100 ], [ 100, 200 ] ],
+ "c": true
+ } ],
+ "e": [ {
+ "i": [ [ 0, 50 ], [ -50, 0 ], [ 0, -50 ], [ 50, 0 ] ],
+ "o": [ [ 0, -50 ], [ 50, 0 ], [ 0, 50 ], [ -50, 0 ] ],
+ "v": [ [ 0, 100 ], [ 100, 0 ], [ 200, 100 ], [ 100, 200 ] ],
+ "c": true
+ } ],
+ "i": { "x":0.5, "y":0.5 },
+ "o": { "x":0.5, "y":0.5 },
+ "t": 100
+ },
+ {
+ "t": 200
+ }
+ ]
+ }
+ },
+
+ {
+ "ty": "st",
+ "nm": "Stroke 1",
+ "lc": 1,
+ "lj": 1,
+ "ml": 4,
+ "w" : { "a": 1, "k": [
+ { "s": [ 30 ], "e": [ 50 ], "i": { "x":0.5, "y":0.5 }, "o": { "x":0.5, "y":0.5 }, "t": 0 },
+ { "s": [ 50 ], "e": [ 30 ], "i": { "x":0.5, "y":0.5 }, "o": { "x":0.5, "y":0.5 }, "t": 100 },
+ { "t": 200 }
+ ] },
+ "o" : { "a": 0, "k": 100 },
+ "c" : { "a": 1, "k": [
+ { "s": [ 0, 1, 0 ], "e": [ 1, 0, 0 ], "i": { "x":0.5, "y":0.5 }, "o": { "x":0.5, "y":0.5 }, "t": 0 },
+ { "s": [ 1, 0, 0 ], "e": [ 0, 1, 0 ], "i": { "x":0.5, "y":0.5 }, "o": { "x":0.5, "y":0.5 }, "t": 100 },
+ { "t": 200 }
+ ] }
+ },
+
+ {
+ "ty":"tr",
+ "p" : { "a":0, "k":[ 0, 0 ] },
+ "a" : { "a":0, "k":[ 0, 0 ] },
+ "s" : { "a":0, "k":[ 100, 100 ] },
+ "r" : { "a":0, "k": 0 },
+ "o" : { "a":0, "k":100 },
+ "nm": "Transform"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
diff --git a/tests/VectorDrawableTest/src/com/android/test/dynamic/LottieDrawableTest.java b/tests/VectorDrawableTest/src/com/android/test/dynamic/LottieDrawableTest.java
new file mode 100644
index 000000000000..05eae7b0e642
--- /dev/null
+++ b/tests/VectorDrawableTest/src/com/android/test/dynamic/LottieDrawableTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 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 com.android.test.dynamic;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.LottieDrawable;
+import android.os.Bundle;
+import android.view.View;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Scanner;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class LottieDrawableTest extends Activity {
+ private static final String TAG = "LottieDrawableTest";
+ static final int BACKGROUND = 0xFFF44336;
+
+ class LottieDrawableView extends View {
+ private Rect mLottieBounds;
+
+ private LottieDrawable mLottie;
+
+ LottieDrawableView(Context context, InputStream is) {
+ super(context);
+ Scanner s = new Scanner(is).useDelimiter("\\A");
+ String json = s.hasNext() ? s.next() : "";
+ try {
+ mLottie = LottieDrawable.makeLottieDrawable(json);
+ } catch (IOException e) {
+ throw new RuntimeException(TAG + ": error parsing test Lottie");
+ }
+ mLottie.start();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ canvas.drawColor(BACKGROUND);
+
+ mLottie.setBounds(mLottieBounds);
+ mLottie.draw(canvas);
+ }
+
+ public void setLottieSize(Rect bounds) {
+ mLottieBounds = bounds;
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ InputStream is = getResources().openRawResource(R.raw.lottie);
+
+ LottieDrawableView view = new LottieDrawableView(this, is);
+ view.setLottieSize(new Rect(0, 0, 900, 900));
+ setContentView(view);
+ }
+}
--
cgit v1.2.3-59-g8ed1b
From 3217d3dcdc4f3b93e2a9228187d5c3bc1dca89ec Mon Sep 17 00:00:00 2001
From: Haoyu Zhang
Date: Tue, 10 Jan 2023 12:59:15 -0800
Subject: Address API feedback for getRunCharacterAdvance
Bug: 241167328
Test: N/A
Change-Id: I0d66c55d31802e555d8b171bd36984bb3496f189
---
graphics/java/android/graphics/Paint.java | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
(limited to 'graphics/java')
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index f0e496f3a178..d35dcab11f49 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -3151,10 +3151,10 @@ public class Paint {
* @see #getRunAdvance(char[], int, int, int, int, boolean, int) for more details.
*
* @param text the text to measure. Cannot be null.
- * @param start the index of the start of the range to measure
- * @param end the index + 1 of the end of the range to measure
- * @param contextStart the index of the start of the shaping context
- * @param contextEnd the index + 1 of the end of the shaping context
+ * @param start the start index of the range to measure, inclusive
+ * @param end the end index of the range to measure, exclusive
+ * @param contextStart the start index of the shaping context, inclusive
+ * @param contextEnd the end index of the shaping context, exclusive
* @param isRtl whether the run is in RTL direction
* @param offset index of caret position
* @param advances the array that receives the computed character advances
--
cgit v1.2.3-59-g8ed1b
From 3b2c0ce8fe8119b5784a7ced1ecc2f8d58635e0b Mon Sep 17 00:00:00 2001
From: Dichen Zhang
Date: Wed, 14 Dec 2022 19:58:55 +0000
Subject: Encode JPEG/R from YuvImage
Test: YuvImageTest
Bug: b/252835416
Change-Id: I010b4498487bf58a0eb1dec3f619fec60b0191aa
---
core/api/current.txt | 3 +
graphics/java/android/graphics/YuvImage.java | 208 +++++++++++++++++++++++++--
libs/hwui/Android.bp | 11 +-
libs/hwui/jni/YuvToJpegEncoder.cpp | 121 +++++++++++++++-
libs/hwui/jni/YuvToJpegEncoder.h | 47 +++++-
5 files changed, 372 insertions(+), 18 deletions(-)
(limited to 'graphics/java')
diff --git a/core/api/current.txt b/core/api/current.txt
index 801da28af206..90f9dba3a3c5 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -16338,7 +16338,10 @@ package android.graphics {
public class YuvImage {
ctor public YuvImage(byte[], int, int, int, int[]);
+ ctor public YuvImage(@NonNull byte[], int, int, int, @Nullable int[], @NonNull android.graphics.ColorSpace);
method public boolean compressToJpeg(android.graphics.Rect, int, java.io.OutputStream);
+ method public boolean compressToJpegR(@NonNull android.graphics.YuvImage, int, @NonNull java.io.OutputStream);
+ method @NonNull public android.graphics.ColorSpace getColorSpace();
method public int getHeight();
method public int[] getStrides();
method public int getWidth();
diff --git a/graphics/java/android/graphics/YuvImage.java b/graphics/java/android/graphics/YuvImage.java
index af3f27661c84..6b5238b20cdc 100644
--- a/graphics/java/android/graphics/YuvImage.java
+++ b/graphics/java/android/graphics/YuvImage.java
@@ -16,6 +16,8 @@
package android.graphics;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import java.io.OutputStream;
/**
@@ -63,7 +65,70 @@ public class YuvImage {
private int mHeight;
/**
- * Construct an YuvImage.
+ * The color space of the image, defaults to SRGB
+ */
+ @NonNull private ColorSpace mColorSpace;
+
+ /**
+ * Array listing all supported ImageFormat that are supported by this class
+ */
+ private final static String[] sSupportedFormats =
+ {"NV21", "YUY2", "YCBCR_P010", "YUV_420_888"};
+
+ private static String printSupportedFormats() {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < sSupportedFormats.length; ++i) {
+ sb.append(sSupportedFormats[i]);
+ if (i != sSupportedFormats.length - 1) {
+ sb.append(", ");
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Array listing all supported HDR ColorSpaces that are supported by JPEG/R encoding
+ */
+ private final static ColorSpace.Named[] sSupportedJpegRHdrColorSpaces = {
+ ColorSpace.Named.BT2020_HLG,
+ ColorSpace.Named.BT2020_PQ
+ };
+
+ /**
+ * Array listing all supported SDR ColorSpaces that are supported by JPEG/R encoding
+ */
+ private final static ColorSpace.Named[] sSupportedJpegRSdrColorSpaces = {
+ ColorSpace.Named.SRGB,
+ ColorSpace.Named.DISPLAY_P3
+ };
+
+ private static String printSupportedJpegRColorSpaces(boolean isHdr) {
+ ColorSpace.Named[] colorSpaces = isHdr ? sSupportedJpegRHdrColorSpaces :
+ sSupportedJpegRSdrColorSpaces;
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < colorSpaces.length; ++i) {
+ sb.append(ColorSpace.get(colorSpaces[i]).getName());
+ if (i != colorSpaces.length - 1) {
+ sb.append(", ");
+ }
+ }
+ return sb.toString();
+ }
+
+ private static boolean isSupportedJpegRColorSpace(boolean isHdr, int colorSpace) {
+ ColorSpace.Named[] colorSpaces = isHdr ? sSupportedJpegRHdrColorSpaces :
+ sSupportedJpegRSdrColorSpaces;
+ for (ColorSpace.Named cs : colorSpaces) {
+ if (cs.ordinal() == colorSpace) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ /**
+ * Construct an YuvImage. Use SRGB for as default {@link ColorSpace}.
*
* @param yuv The YUV data. In the case of more than one image plane, all the planes must be
* concatenated into a single byte array.
@@ -77,11 +142,33 @@ public class YuvImage {
* null.
*/
public YuvImage(byte[] yuv, int format, int width, int height, int[] strides) {
+ this(yuv, format, width, height, strides, ColorSpace.get(ColorSpace.Named.SRGB));
+ }
+
+ /**
+ * Construct an YuvImage.
+ *
+ * @param yuv The YUV data. In the case of more than one image plane, all the planes
+ * must be concatenated into a single byte array.
+ * @param format The YUV data format as defined in {@link ImageFormat}.
+ * @param width The width of the YuvImage.
+ * @param height The height of the YuvImage.
+ * @param strides (Optional) Row bytes of each image plane. If yuv contains padding, the
+ * stride of each image must be provided. If strides is null, the method
+ * assumes no padding and derives the row bytes by format and width itself.
+ * @param colorSpace The YUV image color space as defined in {@link ColorSpace}.
+ * If the parameter is null, SRGB will be set as the default value.
+ * @throws IllegalArgumentException if format is not support; width or height <= 0; or yuv is
+ * null.
+ */
+ public YuvImage(@NonNull byte[] yuv, int format, int width, int height,
+ @Nullable int[] strides, @NonNull ColorSpace colorSpace) {
if (format != ImageFormat.NV21 &&
- format != ImageFormat.YUY2) {
+ format != ImageFormat.YUY2 &&
+ format != ImageFormat.YCBCR_P010 &&
+ format != ImageFormat.YUV_420_888) {
throw new IllegalArgumentException(
- "only support ImageFormat.NV21 " +
- "and ImageFormat.YUY2 for now");
+ "only supports the following ImageFormat:" + printSupportedFormats());
}
if (width <= 0 || height <= 0) {
@@ -93,6 +180,10 @@ public class YuvImage {
throw new IllegalArgumentException("yuv cannot be null");
}
+ if (colorSpace == null) {
+ throw new IllegalArgumentException("ColorSpace cannot be null");
+ }
+
if (strides == null) {
mStrides = calculateStrides(width, format);
} else {
@@ -103,12 +194,13 @@ public class YuvImage {
mFormat = format;
mWidth = width;
mHeight = height;
+ mColorSpace = colorSpace;
}
/**
* Compress a rectangle region in the YuvImage to a jpeg.
- * Only ImageFormat.NV21 and ImageFormat.YUY2
- * are supported for now.
+ * For image format, only ImageFormat.NV21 and ImageFormat.YUY2 are supported.
+ * For color space, only SRGB is supported.
*
* @param rectangle The rectangle region to be compressed. The medthod checks if rectangle is
* inside the image. Also, the method modifies rectangle if the chroma pixels
@@ -117,10 +209,18 @@ public class YuvImage {
* small size, 100 meaning compress for max quality.
* @param stream OutputStream to write the compressed data.
* @return True if the compression is successful.
- * @throws IllegalArgumentException if rectangle is invalid; quality is not within [0,
- * 100]; or stream is null.
+ * @throws IllegalArgumentException if rectangle is invalid; color space or image format
+ * is not supported; quality is not within [0, 100]; or stream is null.
*/
public boolean compressToJpeg(Rect rectangle, int quality, OutputStream stream) {
+ if (mFormat != ImageFormat.NV21 && mFormat != ImageFormat.YUY2) {
+ throw new IllegalArgumentException(
+ "Only ImageFormat.NV21 and ImageFormat.YUY2 are supported.");
+ }
+ if (mColorSpace.getId() != ColorSpace.Named.SRGB.ordinal()) {
+ throw new IllegalArgumentException("Only SRGB color space is supported.");
+ }
+
Rect wholeImage = new Rect(0, 0, mWidth, mHeight);
if (!wholeImage.contains(rectangle)) {
throw new IllegalArgumentException(
@@ -143,6 +243,70 @@ public class YuvImage {
new byte[WORKING_COMPRESS_STORAGE]);
}
+ /**
+ * Compress the HDR image into JPEG/R format.
+ *
+ * Sample usage:
+ * hdr_image.compressToJpegR(sdr_image, 90, stream);
+ *
+ * For the SDR image, only YUV_420_888 image format is supported, and the following
+ * color spaces are supported:
+ * ColorSpace.Named.SRGB,
+ * ColorSpace.Named.DISPLAY_P3
+ *
+ * For the HDR image, only YCBCR_P010 image format is supported, and the following
+ * color spaces are supported:
+ * ColorSpace.Named.BT2020_HLG,
+ * ColorSpace.Named.BT2020_PQ
+ *
+ * @param sdr The SDR image, only ImageFormat.YUV_420_888 is supported.
+ * @param quality Hint to the compressor, 0-100. 0 meaning compress for
+ * small size, 100 meaning compress for max quality.
+ * @param stream OutputStream to write the compressed data.
+ * @return True if the compression is successful.
+ * @throws IllegalArgumentException if input images are invalid; quality is not within [0,
+ * 100]; or stream is null.
+ */
+ public boolean compressToJpegR(@NonNull YuvImage sdr, int quality,
+ @NonNull OutputStream stream) {
+ if (sdr == null) {
+ throw new IllegalArgumentException("SDR input cannot be null");
+ }
+
+ if (mData.length == 0 || sdr.getYuvData().length == 0) {
+ throw new IllegalArgumentException("Input images cannot be empty");
+ }
+
+ if (mFormat != ImageFormat.YCBCR_P010 || sdr.getYuvFormat() != ImageFormat.YUV_420_888) {
+ throw new IllegalArgumentException(
+ "only support ImageFormat.YCBCR_P010 and ImageFormat.YUV_420_888");
+ }
+
+ if (sdr.getWidth() != mWidth || sdr.getHeight() != mHeight) {
+ throw new IllegalArgumentException("HDR and SDR resolution mismatch");
+ }
+
+ if (quality < 0 || quality > 100) {
+ throw new IllegalArgumentException("quality must be 0..100");
+ }
+
+ if (stream == null) {
+ throw new IllegalArgumentException("stream cannot be null");
+ }
+
+ if (!isSupportedJpegRColorSpace(true, mColorSpace.getId()) ||
+ !isSupportedJpegRColorSpace(false, sdr.getColorSpace().getId())) {
+ throw new IllegalArgumentException("Not supported color space. "
+ + "SDR only supports: " + printSupportedJpegRColorSpaces(false)
+ + "HDR only supports: " + printSupportedJpegRColorSpaces(true));
+ }
+
+ return nativeCompressToJpegR(mData, mColorSpace.getDataSpace(),
+ sdr.getYuvData(), sdr.getColorSpace().getDataSpace(),
+ mWidth, mHeight, quality, stream,
+ new byte[WORKING_COMPRESS_STORAGE]);
+ }
+
/**
* @return the YUV data.
@@ -179,6 +343,12 @@ public class YuvImage {
return mHeight;
}
+
+ /**
+ * @return the color space of the image.
+ */
+ public @NonNull ColorSpace getColorSpace() { return mColorSpace; }
+
int[] calculateOffsets(int left, int top) {
int[] offsets = null;
if (mFormat == ImageFormat.NV21) {
@@ -198,17 +368,23 @@ public class YuvImage {
private int[] calculateStrides(int width, int format) {
int[] strides = null;
- if (format == ImageFormat.NV21) {
+ switch (format) {
+ case ImageFormat.NV21:
strides = new int[] {width, width};
return strides;
- }
-
- if (format == ImageFormat.YUY2) {
+ case ImageFormat.YCBCR_P010:
+ strides = new int[] {width * 2, width * 2};
+ return strides;
+ case ImageFormat.YUV_420_888:
+ strides = new int[] {width, (width + 1) / 2, (width + 1) / 2};
+ return strides;
+ case ImageFormat.YUY2:
strides = new int[] {width * 2};
return strides;
+ default:
+ throw new IllegalArgumentException(
+ "only supports the following ImageFormat:" + printSupportedFormats());
}
-
- return strides;
}
private void adjustRectangle(Rect rect) {
@@ -237,4 +413,8 @@ public class YuvImage {
private static native boolean nativeCompressToJpeg(byte[] oriYuv,
int format, int width, int height, int[] offsets, int[] strides,
int quality, OutputStream stream, byte[] tempStorage);
+
+ private static native boolean nativeCompressToJpegR(byte[] hdr, int hdrColorSpaceId,
+ byte[] sdr, int sdrColorSpaceId, int width, int height, int quality,
+ OutputStream stream, byte[] tempStorage);
}
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index aeead5efc48a..9112b1b40c44 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -376,7 +376,10 @@ cc_defaults {
"jni/text/TextShaper.cpp",
],
- header_libs: ["android_graphics_jni_headers"],
+ header_libs: [
+ "android_graphics_jni_headers",
+ "libnativewindow_headers",
+ ],
include_dirs: [
"external/skia/include/private",
@@ -392,10 +395,14 @@ cc_defaults {
"libbase",
"libcutils",
"libharfbuzz_ng",
+ "libimage_io",
+ "libjpeg",
+ "libjpegdecoder",
+ "libjpegencoder",
+ "libjpegrecoverymap",
"liblog",
"libminikin",
"libz",
- "libjpeg",
],
static_libs: [
diff --git a/libs/hwui/jni/YuvToJpegEncoder.cpp b/libs/hwui/jni/YuvToJpegEncoder.cpp
index 1c5f126d8672..80bca1f3a72f 100644
--- a/libs/hwui/jni/YuvToJpegEncoder.cpp
+++ b/libs/hwui/jni/YuvToJpegEncoder.cpp
@@ -1,3 +1,6 @@
+#undef LOG_TAG
+#define LOG_TAG "YuvToJpegEncoder"
+
#include "CreateJavaOutputStreamAdaptor.h"
#include "SkJPEGWriteUtility.h"
#include "SkStream.h"
@@ -235,6 +238,99 @@ void Yuv422IToJpegEncoder::configSamplingFactors(jpeg_compress_struct* cinfo) {
}
///////////////////////////////////////////////////////////////////////////////
+using namespace android::recoverymap;
+
+jpegr_color_gamut P010Yuv420ToJpegREncoder::findColorGamut(JNIEnv* env, int aDataSpace) {
+ switch (aDataSpace & ADataSpace::STANDARD_MASK) {
+ case ADataSpace::STANDARD_BT709:
+ return jpegr_color_gamut::JPEGR_COLORGAMUT_BT709;
+ case ADataSpace::STANDARD_DCI_P3:
+ return jpegr_color_gamut::JPEGR_COLORGAMUT_P3;
+ case ADataSpace::STANDARD_BT2020:
+ return jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100;
+ default:
+ jclass IllegalArgumentException = env->FindClass("java/lang/IllegalArgumentException");
+ env->ThrowNew(IllegalArgumentException,
+ "The requested color gamut is not supported by JPEG/R.");
+ }
+
+ return jpegr_color_gamut::JPEGR_COLORGAMUT_UNSPECIFIED;
+}
+
+jpegr_transfer_function P010Yuv420ToJpegREncoder::findHdrTransferFunction(JNIEnv* env,
+ int aDataSpace) {
+ switch (aDataSpace & ADataSpace::TRANSFER_MASK) {
+ case ADataSpace::TRANSFER_ST2084:
+ return jpegr_transfer_function::JPEGR_TF_PQ;
+ case ADataSpace::TRANSFER_HLG:
+ return jpegr_transfer_function::JPEGR_TF_HLG;
+ default:
+ jclass IllegalArgumentException = env->FindClass("java/lang/IllegalArgumentException");
+ env->ThrowNew(IllegalArgumentException,
+ "The requested HDR transfer function is not supported by JPEG/R.");
+ }
+
+ return jpegr_transfer_function::JPEGR_TF_UNSPECIFIED;
+}
+
+bool P010Yuv420ToJpegREncoder::encode(JNIEnv* env,
+ SkWStream* stream, void* hdr, int hdrColorSpace, void* sdr, int sdrColorSpace,
+ int width, int height, int jpegQuality) {
+ // Check SDR color space. Now we only support SRGB transfer function
+ if ((sdrColorSpace & ADataSpace::TRANSFER_MASK) != ADataSpace::TRANSFER_SRGB) {
+ jclass IllegalArgumentException = env->FindClass("java/lang/IllegalArgumentException");
+ env->ThrowNew(IllegalArgumentException,
+ "The requested SDR color space is not supported. Transfer function must be SRGB");
+ return false;
+ }
+
+ jpegr_color_gamut hdrColorGamut = findColorGamut(env, hdrColorSpace);
+ jpegr_color_gamut sdrColorGamut = findColorGamut(env, sdrColorSpace);
+ jpegr_transfer_function hdrTransferFunction = findHdrTransferFunction(env, hdrColorSpace);
+
+ if (hdrColorGamut == jpegr_color_gamut::JPEGR_COLORGAMUT_UNSPECIFIED
+ || sdrColorGamut == jpegr_color_gamut::JPEGR_COLORGAMUT_UNSPECIFIED
+ || hdrTransferFunction == jpegr_transfer_function::JPEGR_TF_UNSPECIFIED) {
+ return false;
+ }
+
+ RecoveryMap recoveryMap;
+
+ jpegr_uncompressed_struct p010;
+ p010.data = hdr;
+ p010.width = width;
+ p010.height = height;
+ p010.colorGamut = hdrColorGamut;
+
+ jpegr_uncompressed_struct yuv420;
+ yuv420.data = sdr;
+ yuv420.width = width;
+ yuv420.height = height;
+ yuv420.colorGamut = sdrColorGamut;
+
+ jpegr_compressed_struct jpegR;
+ jpegR.maxLength = width * height * sizeof(uint8_t);
+
+ std::unique_ptr jpegr_data = std::make_unique(jpegR.maxLength);
+ jpegR.data = jpegr_data.get();
+
+ if (int success = recoveryMap.encodeJPEGR(&p010, &yuv420,
+ hdrTransferFunction,
+ &jpegR, jpegQuality, nullptr); success != android::OK) {
+ ALOGW("Encode JPEG/R failed, error code: %d.", success);
+ return false;
+ }
+
+ if (!stream->write(jpegR.data, jpegR.length)) {
+ ALOGW("Writing JPEG/R to stream failed.");
+ return false;
+ }
+
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
static jboolean YuvImage_compressToJpeg(JNIEnv* env, jobject, jbyteArray inYuv,
jint format, jint width, jint height, jintArray offsets,
jintArray strides, jint jpegQuality, jobject jstream,
@@ -258,11 +354,34 @@ static jboolean YuvImage_compressToJpeg(JNIEnv* env, jobject, jbyteArray inYuv,
delete strm;
return result;
}
+
+static jboolean YuvImage_compressToJpegR(JNIEnv* env, jobject, jbyteArray inHdr,
+ jint hdrColorSpace, jbyteArray inSdr, jint sdrColorSpace,
+ jint width, jint height, jint quality, jobject jstream,
+ jbyteArray jstorage) {
+ jbyte* hdr = env->GetByteArrayElements(inHdr, NULL);
+ jbyte* sdr = env->GetByteArrayElements(inSdr, NULL);
+ SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage);
+ P010Yuv420ToJpegREncoder encoder;
+
+ jboolean result = JNI_FALSE;
+ if (encoder.encode(env, strm, hdr, hdrColorSpace, sdr, sdrColorSpace,
+ width, height, quality)) {
+ result = JNI_TRUE;
+ }
+
+ env->ReleaseByteArrayElements(inHdr, hdr, 0);
+ env->ReleaseByteArrayElements(inSdr, sdr, 0);
+ delete strm;
+ return result;
+}
///////////////////////////////////////////////////////////////////////////////
static const JNINativeMethod gYuvImageMethods[] = {
{ "nativeCompressToJpeg", "([BIII[I[IILjava/io/OutputStream;[B)Z",
- (void*)YuvImage_compressToJpeg }
+ (void*)YuvImage_compressToJpeg },
+ { "nativeCompressToJpegR", "([BI[BIIIILjava/io/OutputStream;[B)Z",
+ (void*)YuvImage_compressToJpegR }
};
int register_android_graphics_YuvImage(JNIEnv* env)
diff --git a/libs/hwui/jni/YuvToJpegEncoder.h b/libs/hwui/jni/YuvToJpegEncoder.h
index a69726b17e9d..3d6d1f3fd3dd 100644
--- a/libs/hwui/jni/YuvToJpegEncoder.h
+++ b/libs/hwui/jni/YuvToJpegEncoder.h
@@ -1,6 +1,9 @@
#ifndef _ANDROID_GRAPHICS_YUV_TO_JPEG_ENCODER_H_
#define _ANDROID_GRAPHICS_YUV_TO_JPEG_ENCODER_H_
+#include
+#include
+
extern "C" {
#include "jpeglib.h"
#include "jerror.h"
@@ -24,7 +27,7 @@ public:
*
* @param stream The jpeg output stream.
* @param inYuv The input yuv data.
- * @param width Width of the the Yuv data in terms of pixels.
+ * @param width Width of the Yuv data in terms of pixels.
* @param height Height of the Yuv data in terms of pixels.
* @param offsets The offsets in each image plane with respect to inYuv.
* @param jpegQuality Picture quality in [0, 100].
@@ -71,4 +74,46 @@ private:
uint8_t* vRows, int rowIndex, int width, int height);
};
+class P010Yuv420ToJpegREncoder {
+public:
+ /** Encode YUV data to jpeg/r, which is output to a stream.
+ * This method will call RecoveryMap::EncodeJPEGR() method. If encoding failed,
+ * Corresponding error code (defined in jpegrerrorcode.h) will be printed and this
+ * method will be terminated and return false.
+ *
+ * @param env JNI environment.
+ * @param stream The jpeg output stream.
+ * @param hdr The input yuv data (p010 format).
+ * @param hdrColorSpaceId color space id for the input hdr.
+ * @param sdr The input yuv data (yuv420p format).
+ * @param sdrColorSpaceId color space id for the input sdr.
+ * @param width Width of the Yuv data in terms of pixels.
+ * @param height Height of the Yuv data in terms of pixels.
+ * @param jpegQuality Picture quality in [0, 100].
+ * @return true if successfully compressed the stream.
+ */
+ bool encode(JNIEnv* env,
+ SkWStream* stream, void* hdr, int hdrColorSpace, void* sdr, int sdrColorSpace,
+ int width, int height, int jpegQuality);
+
+ /** Map data space (defined in DataSpace.java and data_space.h) to the color gamut
+ * used in JPEG/R
+ *
+ * @param env JNI environment.
+ * @param aDataSpace data space defined in data_space.h.
+ * @return color gamut for JPEG/R.
+ */
+ static android::recoverymap::jpegr_color_gamut findColorGamut(JNIEnv* env, int aDataSpace);
+
+ /** Map data space (defined in DataSpace.java and data_space.h) to the transfer function
+ * used in JPEG/R
+ *
+ * @param env JNI environment.
+ * @param aDataSpace data space defined in data_space.h.
+ * @return color gamut for JPEG/R.
+ */
+ static android::recoverymap::jpegr_transfer_function findHdrTransferFunction(
+ JNIEnv* env, int aDataSpace);
+};
+
#endif // _ANDROID_GRAPHICS_YUV_TO_JPEG_ENCODER_H_
--
cgit v1.2.3-59-g8ed1b
From 659fd2021168aa0ccebd9c357c08639021826210 Mon Sep 17 00:00:00 2001
From: Jorge Betancourt
Date: Thu, 19 Jan 2023 19:21:26 +0000
Subject: Revert "add support for rendering lottie animations through a
LottieDrawable"
This reverts commit 2e58b5c4b9f7d96ce84ccc0dabe2fcd3667052fe.
Reason for revert: prep for mainline
Bug: 257304231
Change-Id: I51515a6eed577ad098020588f12cafc8fff0541e
---
.../android/graphics/drawable/LottieDrawable.java | 151 ---------------------
libs/hwui/Android.bp | 4 -
libs/hwui/SkiaCanvas.cpp | 4 -
libs/hwui/SkiaCanvas.h | 1 -
libs/hwui/apex/jni_runtime.cpp | 2 -
libs/hwui/hwui/Canvas.h | 2 -
libs/hwui/hwui/LottieDrawable.cpp | 83 -----------
libs/hwui/hwui/LottieDrawable.h | 67 ---------
libs/hwui/jni/LottieDrawable.cpp | 91 -------------
libs/hwui/pipeline/skia/SkiaDisplayList.cpp | 10 --
libs/hwui/pipeline/skia/SkiaDisplayList.h | 3 -
libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp | 5 -
libs/hwui/pipeline/skia/SkiaRecordingCanvas.h | 1 -
tests/VectorDrawableTest/Android.bp | 2 -
tests/VectorDrawableTest/AndroidManifest.xml | 9 --
tests/VectorDrawableTest/res/raw/lottie.json | 123 -----------------
.../android/test/dynamic/LottieDrawableTest.java | 76 -----------
17 files changed, 634 deletions(-)
delete mode 100644 graphics/java/android/graphics/drawable/LottieDrawable.java
delete mode 100644 libs/hwui/hwui/LottieDrawable.cpp
delete mode 100644 libs/hwui/hwui/LottieDrawable.h
delete mode 100644 libs/hwui/jni/LottieDrawable.cpp
delete mode 100644 tests/VectorDrawableTest/res/raw/lottie.json
delete mode 100644 tests/VectorDrawableTest/src/com/android/test/dynamic/LottieDrawableTest.java
(limited to 'graphics/java')
diff --git a/graphics/java/android/graphics/drawable/LottieDrawable.java b/graphics/java/android/graphics/drawable/LottieDrawable.java
deleted file mode 100644
index c1f1b50cf339..000000000000
--- a/graphics/java/android/graphics/drawable/LottieDrawable.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2022 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.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SuppressLint;
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.PixelFormat;
-
-import dalvik.annotation.optimization.FastNative;
-
-import libcore.util.NativeAllocationRegistry;
-
-import java.io.IOException;
-
-/**
- * {@link Drawable} for drawing Lottie files.
- *
- * The framework handles decoding subsequent frames in another thread and
- * updating when necessary. The drawable will only animate while it is being
- * displayed.
- *
- * @hide
- */
-@SuppressLint("NotCloseable")
-public class LottieDrawable extends Drawable implements Animatable {
- private long mNativePtr;
-
- /**
- * Create an animation from the provided JSON string
- * @hide
- */
- private LottieDrawable(@NonNull String animationJson) throws IOException {
- mNativePtr = nCreate(animationJson);
- if (mNativePtr == 0) {
- throw new IOException("could not make LottieDrawable from json");
- }
-
- final long nativeSize = nNativeByteSize(mNativePtr);
- NativeAllocationRegistry registry = new NativeAllocationRegistry(
- LottieDrawable.class.getClassLoader(), nGetNativeFinalizer(), nativeSize);
- registry.registerNativeAllocation(this, mNativePtr);
- }
-
- /**
- * Factory for LottieDrawable, throws IOException if native Skottie Builder fails to parse
- */
- public static LottieDrawable makeLottieDrawable(@NonNull String animationJson)
- throws IOException {
- return new LottieDrawable(animationJson);
- }
-
-
-
- /**
- * Draw the current frame to the Canvas.
- * @hide
- */
- @Override
- public void draw(@NonNull Canvas canvas) {
- if (mNativePtr == 0) {
- throw new IllegalStateException("called draw on empty LottieDrawable");
- }
-
- nDraw(mNativePtr, canvas.getNativeCanvasWrapper());
- }
-
- /**
- * Start the animation. Needs to be called before draw calls.
- * @hide
- */
- @Override
- public void start() {
- if (mNativePtr == 0) {
- throw new IllegalStateException("called start on empty LottieDrawable");
- }
-
- if (nStart(mNativePtr)) {
- invalidateSelf();
- }
- }
-
- /**
- * Stops the animation playback. Does not release anything.
- * @hide
- */
- @Override
- public void stop() {
- if (mNativePtr == 0) {
- throw new IllegalStateException("called stop on empty LottieDrawable");
- }
- nStop(mNativePtr);
- }
-
- /**
- * Return whether the animation is currently running.
- */
- @Override
- public boolean isRunning() {
- if (mNativePtr == 0) {
- throw new IllegalStateException("called isRunning on empty LottieDrawable");
- }
- return nIsRunning(mNativePtr);
- }
-
- @Override
- public int getOpacity() {
- // We assume translucency to avoid checking each pixel.
- return PixelFormat.TRANSLUCENT;
- }
-
- @Override
- public void setAlpha(int alpha) {
- //TODO
- }
-
- @Override
- public void setColorFilter(@Nullable ColorFilter colorFilter) {
- //TODO
- }
-
- private static native long nCreate(String json);
- private static native void nDraw(long nativeInstance, long nativeCanvas);
- @FastNative
- private static native long nGetNativeFinalizer();
- @FastNative
- private static native long nNativeByteSize(long nativeInstance);
- @FastNative
- private static native boolean nIsRunning(long nativeInstance);
- @FastNative
- private static native boolean nStart(long nativeInstance);
- @FastNative
- private static native boolean nStop(long nativeInstance);
-
-}
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 9c4ea0ecbe30..3e3d77b89a9a 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -78,7 +78,6 @@ cc_defaults {
"external/skia/src/utils",
"external/skia/src/gpu",
"external/skia/src/shaders",
- "external/skia/modules/skottie",
],
},
host: {
@@ -375,7 +374,6 @@ cc_defaults {
"external/skia/src/effects",
"external/skia/src/image",
"external/skia/src/images",
- "external/skia/modules/skottie",
],
shared_libs: [
@@ -402,7 +400,6 @@ cc_defaults {
"jni/BitmapRegionDecoder.cpp",
"jni/GIFMovie.cpp",
"jni/GraphicsStatsService.cpp",
- "jni/LottieDrawable.cpp",
"jni/Movie.cpp",
"jni/MovieImpl.cpp",
"jni/pdf/PdfDocument.cpp",
@@ -510,7 +507,6 @@ cc_defaults {
"hwui/BlurDrawLooper.cpp",
"hwui/Canvas.cpp",
"hwui/ImageDecoder.cpp",
- "hwui/LottieDrawable.cpp",
"hwui/MinikinSkia.cpp",
"hwui/MinikinUtils.cpp",
"hwui/PaintImpl.cpp",
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index a1c4b49b6742..d83d78f650aa 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -736,10 +736,6 @@ double SkiaCanvas::drawAnimatedImage(AnimatedImageDrawable* imgDrawable) {
return imgDrawable->drawStaging(mCanvas);
}
-void SkiaCanvas::drawLottie(LottieDrawable* lottieDrawable) {
- lottieDrawable->drawStaging(mCanvas);
-}
-
void SkiaCanvas::drawVectorDrawable(VectorDrawableRoot* vectorDrawable) {
vectorDrawable->drawStaging(this);
}
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index fd8b6cdb7ca0..31e3b4c3c7e2 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -145,7 +145,6 @@ public:
float dstTop, float dstRight, float dstBottom,
const Paint* paint) override;
virtual double drawAnimatedImage(AnimatedImageDrawable* imgDrawable) override;
- virtual void drawLottie(LottieDrawable* lottieDrawable) override;
virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override;
diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp
index 5a96174ebac0..e6cfa7bcaf70 100644
--- a/libs/hwui/apex/jni_runtime.cpp
+++ b/libs/hwui/apex/jni_runtime.cpp
@@ -37,7 +37,6 @@ extern int register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv* env);
extern int register_android_graphics_Graphics(JNIEnv* env);
extern int register_android_graphics_ImageDecoder(JNIEnv*);
extern int register_android_graphics_drawable_AnimatedImageDrawable(JNIEnv*);
-extern int register_android_graphics_drawable_LottieDrawable(JNIEnv*);
extern int register_android_graphics_Interpolator(JNIEnv* env);
extern int register_android_graphics_MaskFilter(JNIEnv* env);
extern int register_android_graphics_Movie(JNIEnv* env);
@@ -117,7 +116,6 @@ extern int register_android_view_ThreadedRenderer(JNIEnv* env);
REG_JNI(register_android_graphics_HardwareRendererObserver),
REG_JNI(register_android_graphics_ImageDecoder),
REG_JNI(register_android_graphics_drawable_AnimatedImageDrawable),
- REG_JNI(register_android_graphics_drawable_LottieDrawable),
REG_JNI(register_android_graphics_Interpolator),
REG_JNI(register_android_graphics_MaskFilter),
REG_JNI(register_android_graphics_Matrix),
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 07e2fe24c939..2a2019199bda 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -60,7 +60,6 @@ typedef uirenderer::VectorDrawable::Tree VectorDrawableRoot;
typedef std::function ReadGlyphFunc;
class AnimatedImageDrawable;
-class LottieDrawable;
class Bitmap;
class Paint;
struct Typeface;
@@ -243,7 +242,6 @@ public:
const Paint* paint) = 0;
virtual double drawAnimatedImage(AnimatedImageDrawable* imgDrawable) = 0;
- virtual void drawLottie(LottieDrawable* lottieDrawable) = 0;
virtual void drawPicture(const SkPicture& picture) = 0;
/**
diff --git a/libs/hwui/hwui/LottieDrawable.cpp b/libs/hwui/hwui/LottieDrawable.cpp
deleted file mode 100644
index 92dc51e01a85..000000000000
--- a/libs/hwui/hwui/LottieDrawable.cpp
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2022 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.
- */
-
-#include "LottieDrawable.h"
-
-#include
-#include
-#include
-
-namespace android {
-
-sk_sp LottieDrawable::Make(sk_sp animation, size_t bytesUsed) {
- if (animation) {
- return sk_sp(new LottieDrawable(std::move(animation), bytesUsed));
- }
- return nullptr;
-}
-LottieDrawable::LottieDrawable(sk_sp animation, size_t bytesUsed)
- : mAnimation(std::move(animation)), mBytesUsed(bytesUsed) {}
-
-bool LottieDrawable::start() {
- if (mRunning) {
- return false;
- }
-
- mRunning = true;
- return true;
-}
-
-bool LottieDrawable::stop() {
- bool wasRunning = mRunning;
- mRunning = false;
- return wasRunning;
-}
-
-bool LottieDrawable::isRunning() {
- return mRunning;
-}
-
-// TODO: Check to see if drawable is actually dirty
-bool LottieDrawable::isDirty() {
- return true;
-}
-
-void LottieDrawable::onDraw(SkCanvas* canvas) {
- if (mRunning) {
- const nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
-
- nsecs_t t = 0;
- if (mStartTime == 0) {
- mStartTime = currentTime;
- } else {
- t = currentTime - mStartTime;
- }
- double seekTime = std::fmod((double)t * 1e-9, mAnimation->duration());
- mAnimation->seekFrameTime(seekTime);
- mAnimation->render(canvas);
- }
-}
-
-void LottieDrawable::drawStaging(SkCanvas* canvas) {
- onDraw(canvas);
-}
-
-SkRect LottieDrawable::onGetBounds() {
- // We do not actually know the bounds, so give a conservative answer.
- return SkRectMakeLargest();
-}
-
-} // namespace android
diff --git a/libs/hwui/hwui/LottieDrawable.h b/libs/hwui/hwui/LottieDrawable.h
deleted file mode 100644
index 9cc34bf12f4f..000000000000
--- a/libs/hwui/hwui/LottieDrawable.h
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2022 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.
- */
-
-#pragma once
-
-#include
-#include
-#include
-
-class SkCanvas;
-
-namespace android {
-
-/**
- * Native component of android.graphics.drawable.LottieDrawable.java.
- * This class can be drawn into Canvas.h and maintains the state needed to drive
- * the animation from the RenderThread.
- */
-class LottieDrawable : public SkDrawable {
-public:
- static sk_sp Make(sk_sp animation, size_t bytes);
-
- // Draw to software canvas
- void drawStaging(SkCanvas* canvas);
-
- // Returns true if the animation was started; false otherwise (e.g. it was
- // already running)
- bool start();
- // Returns true if the animation was stopped; false otherwise (e.g. it was
- // already stopped)
- bool stop();
- bool isRunning();
-
- // TODO: Is dirty should take in a time til next frame to determine if it is dirty
- bool isDirty();
-
- SkRect onGetBounds() override;
-
- size_t byteSize() const { return sizeof(*this) + mBytesUsed; }
-
-protected:
- void onDraw(SkCanvas* canvas) override;
-
-private:
- LottieDrawable(sk_sp animation, size_t bytes_used);
-
- sk_sp mAnimation;
- bool mRunning = false;
- // The start time for the drawable itself.
- nsecs_t mStartTime = 0;
- const size_t mBytesUsed = 0;
-};
-
-} // namespace android
diff --git a/libs/hwui/jni/LottieDrawable.cpp b/libs/hwui/jni/LottieDrawable.cpp
deleted file mode 100644
index fb6eede213a8..000000000000
--- a/libs/hwui/jni/LottieDrawable.cpp
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2022 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.
- */
-
-#include
-#include
-#include
-#include
-
-#include "GraphicsJNI.h"
-#include "Utils.h"
-
-using namespace android;
-
-static jclass gLottieDrawableClass;
-
-static jlong LottieDrawable_nCreate(JNIEnv* env, jobject, jstring jjson) {
- const ScopedUtfChars cstr(env, jjson);
- // TODO(b/259267150) provide more accurate byteSize
- size_t bytes = strlen(cstr.c_str());
- auto animation = skottie::Animation::Builder().make(cstr.c_str(), bytes);
- sk_sp drawable(LottieDrawable::Make(std::move(animation), bytes));
- if (!drawable) {
- return 0;
- }
- return reinterpret_cast(drawable.release());
-}
-
-static void LottieDrawable_destruct(LottieDrawable* drawable) {
- SkSafeUnref(drawable);
-}
-
-static jlong LottieDrawable_nGetNativeFinalizer(JNIEnv* /*env*/, jobject /*clazz*/) {
- return static_cast(reinterpret_cast(&LottieDrawable_destruct));
-}
-
-static void LottieDrawable_nDraw(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, jlong canvasPtr) {
- auto* drawable = reinterpret_cast(nativePtr);
- auto* canvas = reinterpret_cast