diff --git a/core/tests/coretests/res/drawable-nodpi/test_too_big.png b/core/tests/coretests/res/drawable-nodpi/test_too_big.png new file mode 100644 index 0000000000000..3754072b8e31e Binary files /dev/null and b/core/tests/coretests/res/drawable-nodpi/test_too_big.png differ diff --git a/core/tests/coretests/src/android/graphics/drawable/IconTest.java b/core/tests/coretests/src/android/graphics/drawable/IconTest.java index 75390a282af9a..5d922961aa8be 100644 --- a/core/tests/coretests/src/android/graphics/drawable/IconTest.java +++ b/core/tests/coretests/src/android/graphics/drawable/IconTest.java @@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.RecordingCanvas; import android.graphics.Region; import android.os.Handler; import android.os.HandlerThread; @@ -371,6 +372,90 @@ public class IconTest extends AndroidTestCase { } } + private int getMaxWidth(int origWidth, int origHeight, int maxNumPixels) { + float aspRatio = (float) origWidth / (float) origHeight; + int newHeight = (int) Math.sqrt(maxNumPixels / aspRatio); + return (int) (newHeight * aspRatio); + } + + private int getMaxHeight(int origWidth, int origHeight, int maxNumPixels) { + float aspRatio = (float) origWidth / (float) origHeight; + return (int) Math.sqrt(maxNumPixels / aspRatio); + } + + @SmallTest + public void testScaleDownMaxSizeWithBitmap() throws Exception { + final int bmpWidth = 13_000; + final int bmpHeight = 10_000; + final int bmpBpp = 4; + final int maxNumPixels = RecordingCanvas.MAX_BITMAP_SIZE / bmpBpp; + final int maxWidth = getMaxWidth(bmpWidth, bmpHeight, maxNumPixels); + final int maxHeight = getMaxHeight(bmpWidth, bmpHeight, maxNumPixels); + + final Bitmap bm = Bitmap.createBitmap(bmpWidth, bmpHeight, Bitmap.Config.ARGB_8888); + final Icon ic = Icon.createWithBitmap(bm); + final Drawable drawable = ic.loadDrawable(mContext); + + assertThat(drawable.getIntrinsicWidth()).isEqualTo(maxWidth); + assertThat(drawable.getIntrinsicHeight()).isEqualTo(maxHeight); + } + + @SmallTest + public void testScaleDownMaxSizeWithAdaptiveBitmap() throws Exception { + final int bmpWidth = 20_000; + final int bmpHeight = 10_000; + final int bmpBpp = 4; + final int maxNumPixels = RecordingCanvas.MAX_BITMAP_SIZE / bmpBpp; + final int maxWidth = getMaxWidth(bmpWidth, bmpHeight, maxNumPixels); + final int maxHeight = getMaxHeight(bmpWidth, bmpHeight, maxNumPixels); + + final Bitmap bm = Bitmap.createBitmap(bmpWidth, bmpHeight, Bitmap.Config.ARGB_8888); + final Icon ic = Icon.createWithAdaptiveBitmap(bm); + final AdaptiveIconDrawable adaptiveDrawable = (AdaptiveIconDrawable) ic.loadDrawable( + mContext); + final Drawable drawable = adaptiveDrawable.getForeground(); + + assertThat(drawable.getIntrinsicWidth()).isEqualTo(maxWidth); + assertThat(drawable.getIntrinsicHeight()).isEqualTo(maxHeight); + } + + @SmallTest + public void testScaleDownMaxSizeWithResource() throws Exception { + final Icon ic = Icon.createWithResource(getContext(), R.drawable.test_too_big); + final BitmapDrawable drawable = (BitmapDrawable) ic.loadDrawable(mContext); + + assertThat(drawable.getBitmap().getByteCount()).isAtMost(RecordingCanvas.MAX_BITMAP_SIZE); + } + + @SmallTest + public void testScaleDownMaxSizeWithFile() throws Exception { + final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.test_too_big)) + .getBitmap(); + final File dir = getContext().getExternalFilesDir(null); + final File file1 = new File(dir, "file1-too-big.png"); + bit1.compress(Bitmap.CompressFormat.PNG, 100, + new FileOutputStream(file1)); + + final Icon ic = Icon.createWithFilePath(file1.toString()); + final BitmapDrawable drawable = (BitmapDrawable) ic.loadDrawable(mContext); + + assertThat(drawable.getBitmap().getByteCount()).isAtMost(RecordingCanvas.MAX_BITMAP_SIZE); + } + + @SmallTest + public void testScaleDownMaxSizeWithData() throws Exception { + final int bmpBpp = 4; + final Bitmap originalBits = ((BitmapDrawable) getContext().getDrawable( + R.drawable.test_too_big)).getBitmap(); + final ByteArrayOutputStream ostream = new ByteArrayOutputStream( + originalBits.getWidth() * originalBits.getHeight() * bmpBpp); + originalBits.compress(Bitmap.CompressFormat.PNG, 100, ostream); + final byte[] pngdata = ostream.toByteArray(); + final Icon ic = Icon.createWithData(pngdata, 0, pngdata.length); + final BitmapDrawable drawable = (BitmapDrawable) ic.loadDrawable(mContext); + + assertThat(drawable.getBitmap().getByteCount()).isAtMost(RecordingCanvas.MAX_BITMAP_SIZE); + } // ======== utils ======== diff --git a/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java index a76d74edc0f4d..708feeb9e421c 100644 --- a/graphics/java/android/graphics/drawable/Icon.java +++ b/graphics/java/android/graphics/drawable/Icon.java @@ -35,6 +35,7 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.BlendMode; import android.graphics.PorterDuff; +import android.graphics.RecordingCanvas; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; @@ -70,6 +71,7 @@ import java.util.Objects; public final class Icon implements Parcelable { private static final String TAG = "Icon"; + private static final boolean DEBUG = false; /** * An icon that was created using {@link Icon#createWithBitmap(Bitmap)}. @@ -360,16 +362,53 @@ public final class Icon implements Parcelable { return result; } + /** + * Resizes image if size too large for Canvas to draw + * @param bitmap Bitmap to be resized if size > {@link RecordingCanvas.MAX_BITMAP_SIZE} + * @return resized bitmap + */ + private Bitmap fixMaxBitmapSize(Bitmap bitmap) { + if (bitmap != null && bitmap.getByteCount() > RecordingCanvas.MAX_BITMAP_SIZE) { + int bytesPerPixel = bitmap.getRowBytes() / bitmap.getWidth(); + int maxNumPixels = RecordingCanvas.MAX_BITMAP_SIZE / bytesPerPixel; + float aspRatio = (float) bitmap.getWidth() / (float) bitmap.getHeight(); + int newHeight = (int) Math.sqrt(maxNumPixels / aspRatio); + int newWidth = (int) (newHeight * aspRatio); + + if (DEBUG) { + Log.d(TAG, + "Image size too large: " + bitmap.getByteCount() + ". Resizing bitmap to: " + + newWidth + " " + newHeight); + } + + return scaleDownIfNecessary(bitmap, newWidth, newHeight); + } + return bitmap; + } + + /** + * Resizes BitmapDrawable if size too large for Canvas to draw + * @param drawable Drawable to be resized if size > {@link RecordingCanvas.MAX_BITMAP_SIZE} + * @return resized Drawable + */ + private Drawable fixMaxBitmapSize(Resources res, Drawable drawable) { + if (drawable instanceof BitmapDrawable) { + Bitmap scaledBmp = fixMaxBitmapSize(((BitmapDrawable) drawable).getBitmap()); + return new BitmapDrawable(res, scaledBmp); + } + return drawable; + } + /** * Do the heavy lifting of loading the drawable, but stop short of applying any tint. */ private Drawable loadDrawableInner(Context context) { switch (mType) { case TYPE_BITMAP: - return new BitmapDrawable(context.getResources(), getBitmap()); + return new BitmapDrawable(context.getResources(), fixMaxBitmapSize(getBitmap())); case TYPE_ADAPTIVE_BITMAP: return new AdaptiveIconDrawable(null, - new BitmapDrawable(context.getResources(), getBitmap())); + new BitmapDrawable(context.getResources(), fixMaxBitmapSize(getBitmap()))); case TYPE_RESOURCE: if (getResources() == null) { // figure out where to load resources from @@ -400,7 +439,8 @@ public final class Icon implements Parcelable { } } try { - return getResources().getDrawable(getResId(), context.getTheme()); + return fixMaxBitmapSize(getResources(), + getResources().getDrawable(getResId(), context.getTheme())); } catch (RuntimeException e) { Log.e(TAG, String.format("Unable to load resource 0x%08x from pkg=%s", getResId(), @@ -409,21 +449,21 @@ public final class Icon implements Parcelable { } break; case TYPE_DATA: - return new BitmapDrawable(context.getResources(), - BitmapFactory.decodeByteArray(getDataBytes(), getDataOffset(), getDataLength()) - ); + return new BitmapDrawable(context.getResources(), fixMaxBitmapSize( + BitmapFactory.decodeByteArray(getDataBytes(), getDataOffset(), + getDataLength()))); case TYPE_URI: InputStream is = getUriInputStream(context); if (is != null) { return new BitmapDrawable(context.getResources(), - BitmapFactory.decodeStream(is)); + fixMaxBitmapSize(BitmapFactory.decodeStream(is))); } break; case TYPE_URI_ADAPTIVE_BITMAP: is = getUriInputStream(context); if (is != null) { return new AdaptiveIconDrawable(null, new BitmapDrawable(context.getResources(), - BitmapFactory.decodeStream(is))); + fixMaxBitmapSize(BitmapFactory.decodeStream(is)))); } break; }