Commit ba3f7d26 authored by Shakti Sahu's avatar Shakti Sahu Committed by Commit Bot

Download Home : Limit scaling of very small images

This CL attempts to limit scaling of very tiny images to a certain factor.
The contents would still be drawn within the rounded rect.
It also separates the full-width image to a different view holder which
would prevent resizing issues of AsyncImageView on reuse.

Bug: 902950
Change-Id: I804ae1ba44db7c875860a395161b5c9eba4e7147
Reviewed-on: https://chromium-review.googlesource.com/c/1297357
Commit-Queue: Shakti Sahu <shaktisahu@chromium.org>
Reviewed-by: default avatarTheresa <twellington@chromium.org>
Reviewed-by: default avatarShakti Sahu <shaktisahu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#606191}
parent 45e55e20
...@@ -182,6 +182,7 @@ class DateOrderedListView { ...@@ -182,6 +182,7 @@ class DateOrderedListView {
boolean isFullWidthMedia = false; boolean isFullWidthMedia = false;
switch (ListUtils.getViewTypeForItem(mModel.get(position), mConfig)) { switch (ListUtils.getViewTypeForItem(mModel.get(position), mConfig)) {
case ListUtils.ViewType.IMAGE: case ListUtils.ViewType.IMAGE:
case ListUtils.ViewType.IMAGE_FULL_WIDTH:
case ListUtils.ViewType.IN_PROGRESS_IMAGE: case ListUtils.ViewType.IN_PROGRESS_IMAGE:
outRect.left = mImagePaddingPx; outRect.left = mImagePaddingPx;
outRect.right = mImagePaddingPx; outRect.right = mImagePaddingPx;
......
...@@ -26,8 +26,8 @@ import java.util.List; ...@@ -26,8 +26,8 @@ import java.util.List;
public class ListUtils { public class ListUtils {
/** The potential types of list items that could be displayed. */ /** The potential types of list items that could be displayed. */
@IntDef({ViewType.DATE, ViewType.IN_PROGRESS, ViewType.GENERIC, ViewType.VIDEO, ViewType.IMAGE, @IntDef({ViewType.DATE, ViewType.IN_PROGRESS, ViewType.GENERIC, ViewType.VIDEO, ViewType.IMAGE,
ViewType.CUSTOM_VIEW, ViewType.PREFETCH, ViewType.SECTION_HEADER, ViewType.IMAGE_FULL_WIDTH, ViewType.CUSTOM_VIEW, ViewType.PREFETCH,
ViewType.IN_PROGRESS_VIDEO, ViewType.IN_PROGRESS_IMAGE}) ViewType.SECTION_HEADER, ViewType.IN_PROGRESS_VIDEO, ViewType.IN_PROGRESS_IMAGE})
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
public @interface ViewType { public @interface ViewType {
int DATE = 0; int DATE = 0;
...@@ -35,11 +35,12 @@ public class ListUtils { ...@@ -35,11 +35,12 @@ public class ListUtils {
int GENERIC = 2; int GENERIC = 2;
int VIDEO = 3; int VIDEO = 3;
int IMAGE = 4; int IMAGE = 4;
int CUSTOM_VIEW = 5; int IMAGE_FULL_WIDTH = 5;
int PREFETCH = 6; int CUSTOM_VIEW = 6;
int SECTION_HEADER = 7; int PREFETCH = 7;
int IN_PROGRESS_VIDEO = 8; int SECTION_HEADER = 8;
int IN_PROGRESS_IMAGE = 9; int IN_PROGRESS_VIDEO = 9;
int IN_PROGRESS_IMAGE = 10;
} }
/** /**
...@@ -95,7 +96,9 @@ public class ListUtils { ...@@ -95,7 +96,9 @@ public class ListUtils {
case OfflineItemFilter.FILTER_VIDEO: case OfflineItemFilter.FILTER_VIDEO:
return inProgress ? ViewType.IN_PROGRESS_VIDEO : ViewType.VIDEO; return inProgress ? ViewType.IN_PROGRESS_VIDEO : ViewType.VIDEO;
case OfflineItemFilter.FILTER_IMAGE: case OfflineItemFilter.FILTER_IMAGE:
return inProgress ? ViewType.IN_PROGRESS_IMAGE : ViewType.IMAGE; return inProgress ? ViewType.IN_PROGRESS_IMAGE
: (offlineItem.spanFullWidth ? ViewType.IMAGE_FULL_WIDTH
: ViewType.IMAGE);
// case OfflineItemFilter.FILTER_PAGE: // case OfflineItemFilter.FILTER_PAGE:
// case OfflineItemFilter.FILTER_AUDIO: // case OfflineItemFilter.FILTER_AUDIO:
// case OfflineItemFilter.FILTER_OTHER: // case OfflineItemFilter.FILTER_OTHER:
...@@ -142,10 +145,6 @@ public class ListUtils { ...@@ -142,10 +145,6 @@ public class ListUtils {
* @see GridLayoutManager.SpanSizeLookup * @see GridLayoutManager.SpanSizeLookup
*/ */
public static int getSpanSize(ListItem item, DownloadManagerUiConfig config, int spanCount) { public static int getSpanSize(ListItem item, DownloadManagerUiConfig config, int spanCount) {
if (item instanceof OfflineItemListItem && ((OfflineItemListItem) item).spanFullWidth) {
return spanCount;
}
switch (getViewTypeForItem(item, config)) { switch (getViewTypeForItem(item, config)) {
case ViewType.IMAGE: // Intentional fallthrough. case ViewType.IMAGE: // Intentional fallthrough.
case ViewType.IN_PROGRESS_IMAGE: case ViewType.IN_PROGRESS_IMAGE:
......
...@@ -35,7 +35,8 @@ public abstract class ListItemViewHolder extends ViewHolder { ...@@ -35,7 +35,8 @@ public abstract class ListItemViewHolder extends ViewHolder {
return GenericViewHolder.create(parent); return GenericViewHolder.create(parent);
case ListUtils.ViewType.VIDEO: case ListUtils.ViewType.VIDEO:
return VideoViewHolder.create(parent); return VideoViewHolder.create(parent);
case ListUtils.ViewType.IMAGE: case ListUtils.ViewType.IMAGE: // intentional fall-through
case ListUtils.ViewType.IMAGE_FULL_WIDTH:
return ImageViewHolder.create(parent); return ImageViewHolder.create(parent);
case ListUtils.ViewType.CUSTOM_VIEW: case ListUtils.ViewType.CUSTOM_VIEW:
return new CustomViewHolder(parent); return new CustomViewHolder(parent);
......
...@@ -4,10 +4,13 @@ ...@@ -4,10 +4,13 @@
package org.chromium.chrome.browser.download.home.list.holder; package org.chromium.chrome.browser.download.home.list.holder;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.support.annotation.CallSuper; import android.support.annotation.CallSuper;
import android.view.View; import android.view.View;
import android.widget.ImageView;
import org.chromium.chrome.browser.download.home.list.ListItem; import org.chromium.chrome.browser.download.home.list.ListItem;
import org.chromium.chrome.browser.download.home.list.ListProperties; import org.chromium.chrome.browser.download.home.list.ListProperties;
...@@ -23,6 +26,8 @@ import org.chromium.components.offline_items_collection.OfflineItemVisuals; ...@@ -23,6 +26,8 @@ import org.chromium.components.offline_items_collection.OfflineItemVisuals;
* Helper that supports all typical actions for OfflineItems. * Helper that supports all typical actions for OfflineItems.
*/ */
class OfflineItemViewHolder extends ListItemViewHolder implements ListMenuButton.Delegate { class OfflineItemViewHolder extends ListItemViewHolder implements ListMenuButton.Delegate {
private static final float IMAGE_VIEW_MAX_SCALE_FACTOR = 4.f;
/** The {@link View} that visually represents the selected state of this list item. */ /** The {@link View} that visually represents the selected state of this list item. */
protected final SelectionView mSelectionView; protected final SelectionView mSelectionView;
...@@ -89,6 +94,10 @@ class OfflineItemViewHolder extends ListItemViewHolder implements ListMenuButton ...@@ -89,6 +94,10 @@ class OfflineItemViewHolder extends ListItemViewHolder implements ListMenuButton
mThumbnail.setAsyncImageDrawable((consumer, width, height) -> { mThumbnail.setAsyncImageDrawable((consumer, width, height) -> {
return properties.get(ListProperties.PROVIDER_VISUALS) return properties.get(ListProperties.PROVIDER_VISUALS)
.getVisuals(offlineItem, width, height, (id, visuals) -> { .getVisuals(offlineItem, width, height, (id, visuals) -> {
Matrix matrix = upscaleBitmapIfNecessary(mThumbnail, visuals);
mThumbnail.setImageMatrix(matrix);
mThumbnail.setScaleType(matrix == null ? ImageView.ScaleType.CENTER_CROP
: ImageView.ScaleType.MATRIX);
consumer.onResult(onThumbnailRetrieved(visuals)); consumer.onResult(onThumbnailRetrieved(visuals));
}); });
}, offlineItem.id); }, offlineItem.id);
...@@ -140,4 +149,34 @@ class OfflineItemViewHolder extends ListItemViewHolder implements ListMenuButton ...@@ -140,4 +149,34 @@ class OfflineItemViewHolder extends ListItemViewHolder implements ListMenuButton
|| mSelectionView.isInSelectionMode() || mSelectionView.isInSelectionMode()
!= properties.get(ListProperties.SELECTION_MODE_ACTIVE); != properties.get(ListProperties.SELECTION_MODE_ACTIVE);
} }
private Matrix upscaleBitmapIfNecessary(ImageView view, OfflineItemVisuals visuals) {
Bitmap bitmap = visuals == null ? null : visuals.icon;
if (bitmap == null) return null;
float scale = computeScaleFactor(view, bitmap);
if (scale <= 1.f) return null;
// Compute the required matrix to scale and center the bitmap.
Matrix matrix = new Matrix();
matrix.setScale(scale, scale);
matrix.postTranslate((view.getWidth() - scale * bitmap.getWidth()) * 0.5f,
(view.getHeight() - scale * bitmap.getHeight()) * 0.5f);
return matrix;
}
/**
* Computes a scale factor for the bitmap if the bitmap is too small compared to the view
* dimensions. The scaled bitmap will be centered inside the view. No scaling if the dimensions
* are comparable.
*/
private float computeScaleFactor(ImageView view, Bitmap bitmap) {
float widthRatio = (float) view.getWidth() / bitmap.getWidth();
float heightRatio = (float) view.getHeight() / bitmap.getHeight();
if (Math.max(widthRatio, heightRatio) < IMAGE_VIEW_MAX_SCALE_FACTOR) return 1.f;
float minRequiredScale = Math.min(widthRatio, heightRatio);
return Math.min(minRequiredScale, IMAGE_VIEW_MAX_SCALE_FACTOR);
}
} }
...@@ -11,6 +11,7 @@ import android.graphics.BitmapShader; ...@@ -11,6 +11,7 @@ import android.graphics.BitmapShader;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Shader; import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.ColorDrawable;
...@@ -32,7 +33,8 @@ import org.chromium.ui.R; ...@@ -32,7 +33,8 @@ import org.chromium.ui.R;
* app:cornerRadiusTopStart="8dp" * app:cornerRadiusTopStart="8dp"
* app:cornerRadiusTopEnd="8dp" * app:cornerRadiusTopEnd="8dp"
* app:cornerRadiusBottomStart="8dp" * app:cornerRadiusBottomStart="8dp"
* app:cornerRadiusBottomEnd="8dp" /> * app:cornerRadiusBottomEnd="8dp"
* app:roundedfillColor="@android:color/white"/>
* *
* Note : This does not properly handle padding. Padding will not be taken into account when rounded * Note : This does not properly handle padding. Padding will not be taken into account when rounded
* corners are used. * corners are used.
...@@ -44,6 +46,9 @@ public class RoundedCornerImageView extends ImageView { ...@@ -44,6 +46,9 @@ public class RoundedCornerImageView extends ImageView {
private Paint mFillPaint; private Paint mFillPaint;
// Object to avoid allocations during draw calls.
private final RectF mTmpRect = new RectF();
// Whether or not to apply the shader, if we have one. This might be set to false if the image // Whether or not to apply the shader, if we have one. This might be set to false if the image
// is smaller than the view and does not need to have the corners rounded. // is smaller than the view and does not need to have the corners rounded.
private boolean mApplyShader; private boolean mApplyShader;
...@@ -170,24 +175,43 @@ public class RoundedCornerImageView extends ImageView { ...@@ -170,24 +175,43 @@ public class RoundedCornerImageView extends ImageView {
if (drawFill || drawContent) localRoundedRect.resize(getWidth(), getHeight()); if (drawFill || drawContent) localRoundedRect.resize(getWidth(), getHeight());
// First, fill the drawing area with the given fill paint.
if (drawFill) localRoundedRect.draw(canvas, mFillPaint); if (drawFill) localRoundedRect.draw(canvas, mFillPaint);
if (!drawContent) { if (!drawContent) {
// We probably have an unsupported drawable or we don't want rounded corners. Draw
// normally and return.
super.onDraw(canvas); super.onDraw(canvas);
return; return;
} }
// We have a drawable to draw with rounded corners. Let's first set up the paint.
if (drawable instanceof ColorDrawable) { if (drawable instanceof ColorDrawable) {
ColorDrawable colorDrawable = (ColorDrawable) drawable; ColorDrawable colorDrawable = (ColorDrawable) drawable;
localPaint.setColor(colorDrawable.getColor()); localPaint.setColor(colorDrawable.getColor());
} }
if (mShader != null && mApplyShader) { if (mShader != null && mApplyShader) {
// Apply the matrix to the bitmap shader.
mShader.setLocalMatrix(getImageMatrix()); mShader.setLocalMatrix(getImageMatrix());
localPaint.setShader(mShader); localPaint.setShader(mShader);
// Find the desired bounding box where the bitmap is to be shown.
mTmpRect.set(getDrawable().getBounds());
getImageMatrix().mapRect(mTmpRect);
} }
final int saveCount = canvas.save();
// Clip the canvas to the desired bounding box so that the shader isn't applied anywhere
// outside the desired area.
if (mApplyShader) canvas.clipRect(mTmpRect);
// Draw the rounded rectangle.
localRoundedRect.draw(canvas, localPaint); localRoundedRect.draw(canvas, localPaint);
// Remove the clip.
canvas.restoreToCount(saveCount);
} }
private boolean isSupportedDrawable(Drawable drawable) { private boolean isSupportedDrawable(Drawable drawable) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment