Merge "[DO NOT MERGE] Limit Icon Drawables to MAX_BITMAP_SIZE" into udc-dev

This commit is contained in:
Treehugger Robot
2023-04-18 11:10:52 +00:00
committed by Android (Google) Code Review
3 changed files with 133 additions and 8 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

View File

@@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.RecordingCanvas;
import android.graphics.Region; import android.graphics.Region;
import android.os.Handler; import android.os.Handler;
import android.os.HandlerThread; 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 ======== // ======== utils ========

View File

@@ -35,6 +35,7 @@ import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.graphics.BlendMode; import android.graphics.BlendMode;
import android.graphics.PorterDuff; import android.graphics.PorterDuff;
import android.graphics.RecordingCanvas;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Build; import android.os.Build;
@@ -70,6 +71,7 @@ import java.util.Objects;
public final class Icon implements Parcelable { public final class Icon implements Parcelable {
private static final String TAG = "Icon"; private static final String TAG = "Icon";
private static final boolean DEBUG = false;
/** /**
* An icon that was created using {@link Icon#createWithBitmap(Bitmap)}. * An icon that was created using {@link Icon#createWithBitmap(Bitmap)}.
@@ -360,16 +362,53 @@ public final class Icon implements Parcelable {
return result; 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. * Do the heavy lifting of loading the drawable, but stop short of applying any tint.
*/ */
private Drawable loadDrawableInner(Context context) { private Drawable loadDrawableInner(Context context) {
switch (mType) { switch (mType) {
case TYPE_BITMAP: case TYPE_BITMAP:
return new BitmapDrawable(context.getResources(), getBitmap()); return new BitmapDrawable(context.getResources(), fixMaxBitmapSize(getBitmap()));
case TYPE_ADAPTIVE_BITMAP: case TYPE_ADAPTIVE_BITMAP:
return new AdaptiveIconDrawable(null, return new AdaptiveIconDrawable(null,
new BitmapDrawable(context.getResources(), getBitmap())); new BitmapDrawable(context.getResources(), fixMaxBitmapSize(getBitmap())));
case TYPE_RESOURCE: case TYPE_RESOURCE:
if (getResources() == null) { if (getResources() == null) {
// figure out where to load resources from // figure out where to load resources from
@@ -400,7 +439,8 @@ public final class Icon implements Parcelable {
} }
} }
try { try {
return getResources().getDrawable(getResId(), context.getTheme()); return fixMaxBitmapSize(getResources(),
getResources().getDrawable(getResId(), context.getTheme()));
} catch (RuntimeException e) { } catch (RuntimeException e) {
Log.e(TAG, String.format("Unable to load resource 0x%08x from pkg=%s", Log.e(TAG, String.format("Unable to load resource 0x%08x from pkg=%s",
getResId(), getResId(),
@@ -409,21 +449,21 @@ public final class Icon implements Parcelable {
} }
break; break;
case TYPE_DATA: case TYPE_DATA:
return new BitmapDrawable(context.getResources(), return new BitmapDrawable(context.getResources(), fixMaxBitmapSize(
BitmapFactory.decodeByteArray(getDataBytes(), getDataOffset(), getDataLength()) BitmapFactory.decodeByteArray(getDataBytes(), getDataOffset(),
); getDataLength())));
case TYPE_URI: case TYPE_URI:
InputStream is = getUriInputStream(context); InputStream is = getUriInputStream(context);
if (is != null) { if (is != null) {
return new BitmapDrawable(context.getResources(), return new BitmapDrawable(context.getResources(),
BitmapFactory.decodeStream(is)); fixMaxBitmapSize(BitmapFactory.decodeStream(is)));
} }
break; break;
case TYPE_URI_ADAPTIVE_BITMAP: case TYPE_URI_ADAPTIVE_BITMAP:
is = getUriInputStream(context); is = getUriInputStream(context);
if (is != null) { if (is != null) {
return new AdaptiveIconDrawable(null, new BitmapDrawable(context.getResources(), return new AdaptiveIconDrawable(null, new BitmapDrawable(context.getResources(),
BitmapFactory.decodeStream(is))); fixMaxBitmapSize(BitmapFactory.decodeStream(is))));
} }
break; break;
} }