Skip to content

Commit

Permalink
Download Home : Limit scaling of very small images
Browse files Browse the repository at this point in the history
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: Theresa <twellington@chromium.org>
Reviewed-by: Shakti Sahu <shaktisahu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#606191}
  • Loading branch information
Shakti Sahu authored and Commit Bot committed Nov 7, 2018
1 parent 45e55e2 commit ba3f7d2
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State s
boolean isFullWidthMedia = false;
switch (ListUtils.getViewTypeForItem(mModel.get(position), mConfig)) {
case ListUtils.ViewType.IMAGE:
case ListUtils.ViewType.IMAGE_FULL_WIDTH:
case ListUtils.ViewType.IN_PROGRESS_IMAGE:
outRect.left = mImagePaddingPx;
outRect.right = mImagePaddingPx;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,21 @@
public class ListUtils {
/** The potential types of list items that could be displayed. */
@IntDef({ViewType.DATE, ViewType.IN_PROGRESS, ViewType.GENERIC, ViewType.VIDEO, ViewType.IMAGE,
ViewType.CUSTOM_VIEW, ViewType.PREFETCH, ViewType.SECTION_HEADER,
ViewType.IN_PROGRESS_VIDEO, ViewType.IN_PROGRESS_IMAGE})
ViewType.IMAGE_FULL_WIDTH, ViewType.CUSTOM_VIEW, ViewType.PREFETCH,
ViewType.SECTION_HEADER, ViewType.IN_PROGRESS_VIDEO, ViewType.IN_PROGRESS_IMAGE})
@Retention(RetentionPolicy.SOURCE)
public @interface ViewType {
int DATE = 0;
int IN_PROGRESS = 1;
int GENERIC = 2;
int VIDEO = 3;
int IMAGE = 4;
int CUSTOM_VIEW = 5;
int PREFETCH = 6;
int SECTION_HEADER = 7;
int IN_PROGRESS_VIDEO = 8;
int IN_PROGRESS_IMAGE = 9;
int IMAGE_FULL_WIDTH = 5;
int CUSTOM_VIEW = 6;
int PREFETCH = 7;
int SECTION_HEADER = 8;
int IN_PROGRESS_VIDEO = 9;
int IN_PROGRESS_IMAGE = 10;
}

/**
Expand Down Expand Up @@ -95,7 +96,9 @@ public static List<OfflineItem> toOfflineItems(Collection<ListItem> items) {
case OfflineItemFilter.FILTER_VIDEO:
return inProgress ? ViewType.IN_PROGRESS_VIDEO : ViewType.VIDEO;
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_AUDIO:
// case OfflineItemFilter.FILTER_OTHER:
Expand Down Expand Up @@ -142,10 +145,6 @@ public static List<OfflineItem> toOfflineItems(Collection<ListItem> items) {
* @see GridLayoutManager.SpanSizeLookup
*/
public static int getSpanSize(ListItem item, DownloadManagerUiConfig config, int spanCount) {
if (item instanceof OfflineItemListItem && ((OfflineItemListItem) item).spanFullWidth) {
return spanCount;
}

switch (getViewTypeForItem(item, config)) {
case ViewType.IMAGE: // Intentional fallthrough.
case ViewType.IN_PROGRESS_IMAGE:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ public static ListItemViewHolder create(ViewGroup parent, @ListUtils.ViewType in
return GenericViewHolder.create(parent);
case ListUtils.ViewType.VIDEO:
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);
case ListUtils.ViewType.CUSTOM_VIEW:
return new CustomViewHolder(parent);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@

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.Drawable;
import android.support.annotation.CallSuper;
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.ListProperties;
Expand All @@ -23,6 +26,8 @@
* Helper that supports all typical actions for OfflineItems.
*/
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. */
protected final SelectionView mSelectionView;

Expand Down Expand Up @@ -89,6 +94,10 @@ public void bind(PropertyModel properties, ListItem item) {
mThumbnail.setAsyncImageDrawable((consumer, width, height) -> {
return properties.get(ListProperties.PROVIDER_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));
});
}, offlineItem.id);
Expand Down Expand Up @@ -140,4 +149,34 @@ private boolean shouldPushSelection(PropertyModel properties, ListItem item) {
|| mSelectionView.isInSelectionMode()
!= 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
Expand All @@ -32,7 +33,8 @@
* app:cornerRadiusTopStart="8dp"
* app:cornerRadiusTopEnd="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
* corners are used.
Expand All @@ -44,6 +46,9 @@ public class RoundedCornerImageView extends ImageView {

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
// is smaller than the view and does not need to have the corners rounded.
private boolean mApplyShader;
Expand Down Expand Up @@ -170,24 +175,43 @@ protected void onDraw(Canvas canvas) {

if (drawFill || drawContent) localRoundedRect.resize(getWidth(), getHeight());

// First, fill the drawing area with the given fill paint.
if (drawFill) localRoundedRect.draw(canvas, mFillPaint);

if (!drawContent) {
// We probably have an unsupported drawable or we don't want rounded corners. Draw
// normally and return.
super.onDraw(canvas);
return;
}

// We have a drawable to draw with rounded corners. Let's first set up the paint.
if (drawable instanceof ColorDrawable) {
ColorDrawable colorDrawable = (ColorDrawable) drawable;
localPaint.setColor(colorDrawable.getColor());
}

if (mShader != null && mApplyShader) {
// Apply the matrix to the bitmap shader.
mShader.setLocalMatrix(getImageMatrix());
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);

// Remove the clip.
canvas.restoreToCount(saveCount);
}

private boolean isSupportedDrawable(Drawable drawable) {
Expand Down

0 comments on commit ba3f7d2

Please sign in to comment.