/*
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.drawee.drawable;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import com.facebook.common.internal.Preconditions;
import com.facebook.common.internal.VisibleForTesting;
/**
* Drawable that can scale underlying drawable based on specified {@link ScalingUtils.ScaleType}
* options.
* <p/> Based on {@link android.widget.ImageView.ScaleType}.
*/
public class ScaleTypeDrawable extends ForwardingDrawable {
// Specified scale type.
@VisibleForTesting ScalingUtils.ScaleType mScaleType;
// Specified focus point to use with FOCUS_CROP.
@VisibleForTesting PointF mFocusPoint = null;
// Last known dimensions of the underlying drawable. Used to avoid computing bounds every time
// if underlying size hasn't changed.
@VisibleForTesting int mUnderlyingWidth = 0;
@VisibleForTesting int mUnderlyingHeight = 0;
// Matrix that is actually being used for drawing.
@VisibleForTesting Matrix mDrawMatrix;
// Temporary objects preallocated in advance to save future allocations.
private Matrix mTempMatrix = new Matrix();
/**
* Creates a new ScaleType drawable with given underlying drawable and scale type.
* @param drawable underlying drawable to apply scale type on
* @param scaleType scale type to be applied
*/
public ScaleTypeDrawable(Drawable drawable, ScalingUtils.ScaleType scaleType) {
super(Preconditions.checkNotNull(drawable));
mScaleType = scaleType;
}
/**
* Gets the current scale type.
* @return scale type
*/
public ScalingUtils.ScaleType getScaleType() {
return mScaleType;
}
/**
* Sets the scale type.
* @param scaleType scale type to set
*/
public void setScaleType(ScalingUtils.ScaleType scaleType) {
mScaleType = scaleType;
configureBounds();
invalidateSelf();
}
/**
* Gets the focus point.
* @return focus point of the image
*/
public PointF getFocusPoint() {
return mFocusPoint;
}
/**
* Sets the focus point.
* If ScaleType.FOCUS_CROP is used, focus point will attempted to be centered within a view.
* Each coordinate is a real number in [0,1] range, in the coordinate system where top-left
* corner of the image corresponds to (0, 0) and the bottom-right corner corresponds to (1, 1).
* @param focusPoint focus point of the image
*/
public void setFocusPoint(PointF focusPoint) {
if (mFocusPoint == null) {
mFocusPoint = new PointF();
}
mFocusPoint.set(focusPoint);
configureBounds();
invalidateSelf();
}
@Override
public void draw(Canvas canvas) {
configureBoundsIfUnderlyingChanged();
if (mDrawMatrix != null) {
int saveCount = canvas.save();
canvas.clipRect(getBounds());
canvas.concat(mDrawMatrix);
super.draw(canvas);
canvas.restoreToCount(saveCount);
} else {
// mDrawMatrix == null means our bounds match and we can take fast path
super.draw(canvas);
}
}
@Override
protected void onBoundsChange(Rect bounds) {
configureBounds();
}
private void configureBoundsIfUnderlyingChanged() {
if (mUnderlyingWidth != getCurrent().getIntrinsicWidth() ||
mUnderlyingHeight != getCurrent().getIntrinsicHeight()) {
configureBounds();
}
}
/**
* Determines bounds for the underlying drawable and a matrix that should be applied on it.
* Adopted from android.widget.ImageView
*/
@VisibleForTesting void configureBounds() {
Drawable underlyingDrawable = getCurrent();
Rect bounds = getBounds();
int viewWidth = bounds.width();
int viewHeight = bounds.height();
int underlyingWidth = mUnderlyingWidth = underlyingDrawable.getIntrinsicWidth();
int underlyingHeight = mUnderlyingHeight = underlyingDrawable.getIntrinsicHeight();
// If the drawable has no intrinsic size, we just fill our entire view.
if (underlyingWidth <= 0 || underlyingHeight <= 0) {
underlyingDrawable.setBounds(bounds);
mDrawMatrix = null;
return;
}
// If the drawable fits exactly, no transform needed.
if (underlyingWidth == viewWidth && underlyingHeight == viewHeight) {
underlyingDrawable.setBounds(bounds);
mDrawMatrix = null;
return;
}
// If we're told to scale to fit, we just fill our entire view.
// (ScalingUtils.getTransform would do, but this is faster)
if (mScaleType == ScalingUtils.ScaleType.FIT_XY) {
underlyingDrawable.setBounds(bounds);
mDrawMatrix = null;
return;
}
// We need to do the scaling ourselves, so have the underlying drawable use its preferred size.
underlyingDrawable.setBounds(0, 0, underlyingWidth, underlyingHeight);
ScalingUtils.getTransform(
mTempMatrix,
bounds,
underlyingWidth,
underlyingHeight,
(mFocusPoint != null) ? mFocusPoint.x : 0.5f,
(mFocusPoint != null) ? mFocusPoint.y : 0.5f,
mScaleType);
mDrawMatrix = mTempMatrix;
}
/**
* TransformationCallback method
* @param transform
*/
@Override
public void getTransform(Matrix transform) {
getParentTransform(transform);
// IMPORTANT: {@code configureBounds} should be called after {@code getParentTransform},
// because the parent may have to change our bounds.
configureBoundsIfUnderlyingChanged();
if (mDrawMatrix != null) {
transform.preConcat(mDrawMatrix);
}
}
}