/* * 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.Matrix; import android.graphics.Rect; import android.graphics.drawable.Drawable; import javax.annotation.Nullable; /** * Performs scale type calculations. */ public class ScalingUtils { /** * Options for scaling the child bounds to the parent bounds. * <p> * Similar to {@link android.widget.ImageView.ScaleType}, but ScaleType.MATRIX is not supported. * To use matrix scaling, use a {@link MatrixDrawable}. An additional scale type (FOCUS_CROP) is * provided. * <p> */ public interface ScaleType { /** * Scales width and height independently, so that the child matches the parent exactly. * This may change the aspect ratio of the child. */ static final ScaleType FIT_XY = ScaleTypeFitXY.INSTANCE; /** * Scales the child so that it fits entirely inside the parent. At least one dimension (width or * height) will fit exactly. Aspect ratio is preserved. * Child is aligned to the top-left corner of the parent. */ static final ScaleType FIT_START = ScaleTypeFitStart.INSTANCE; /** * Scales the child so that it fits entirely inside the parent. At least one dimension (width or * height) will fit exactly. Aspect ratio is preserved. * Child is centered within the parent's bounds. */ static final ScaleType FIT_CENTER = ScaleTypeFitCenter.INSTANCE; /** * Scales the child so that it fits entirely inside the parent. At least one dimension (width or * height) will fit exactly. Aspect ratio is preserved. * Child is aligned to the bottom-right corner of the parent. */ static final ScaleType FIT_END = ScaleTypeFitEnd.INSTANCE; /** * Performs no scaling. * Child is centered within parent's bounds. */ static final ScaleType CENTER = ScaleTypeCenter.INSTANCE; /** * Scales the child so that it fits entirely inside the parent. Unlike FIT_CENTER, if the child * is smaller, no up-scaling will be performed. Aspect ratio is preserved. * Child is centered within parent's bounds. */ static final ScaleType CENTER_INSIDE = ScaleTypeCenterInside.INSTANCE; /** * Scales the child so that both dimensions will be greater than or equal to the corresponding * dimension of the parent. At least one dimension (width or height) will fit exactly. * Child is centered within parent's bounds. */ static final ScaleType CENTER_CROP = ScaleTypeCenterCrop.INSTANCE; /** * Scales the child so that both dimensions will be greater than or equal to the corresponding * dimension of the parent. At least one dimension (width or height) will fit exactly. * The child's focus point will be centered within the parent's bounds as much as possible * without leaving empty space. * It is guaranteed that the focus point will be visible and centered as much as possible. * If the focus point is set to (0.5f, 0.5f), result will be equivalent to CENTER_CROP. */ static final ScaleType FOCUS_CROP = ScaleTypeFocusCrop.INSTANCE; /** * Gets transformation matrix based on the scale type. * @param outTransform out matrix to store result * @param parentBounds parent bounds * @param childWidth child width * @param childHeight child height * @param focusX focus point x coordinate, relative [0...1] * @param focusY focus point y coordinate, relative [0...1] * @return same reference to the out matrix for convenience */ Matrix getTransform( Matrix outTransform, Rect parentBounds, int childWidth, int childHeight, float focusX, float focusY); } @Nullable public static ScaleTypeDrawable getActiveScaleTypeDrawable(Drawable drawable) { if (drawable == null) { return null; } else if (drawable instanceof ScaleTypeDrawable) { return (ScaleTypeDrawable) drawable; } else if (drawable instanceof DrawableParent) { final Drawable childDrawable = ((DrawableParent) drawable).getDrawable(); return getActiveScaleTypeDrawable(childDrawable); } else if (drawable instanceof ArrayDrawable) { final ArrayDrawable fadeDrawable = (ArrayDrawable) drawable; final int numLayers = fadeDrawable.getNumberOfLayers(); for (int i = 0; i < numLayers; i++) { final Drawable childDrawable = fadeDrawable.getDrawable(i); final ScaleTypeDrawable result = getActiveScaleTypeDrawable(childDrawable); if (result != null) { return result; } } } return null; } /** * A convenience base class that has some common logic. */ public static abstract class AbstractScaleType implements ScaleType { @Override public Matrix getTransform( Matrix outTransform, Rect parentRect, int childWidth, int childHeight, float focusX, float focusY) { final float sX = (float) parentRect.width() / (float) childWidth; final float sY = (float) parentRect.height() / (float) childHeight; getTransformImpl(outTransform, parentRect, childWidth, childHeight, focusX, focusY, sX, sY); return outTransform; } public abstract void getTransformImpl( Matrix outTransform, Rect parentRect, int childWidth, int childHeight, float focusX, float focusY, float scaleX, float scaleY); } private static class ScaleTypeFitXY extends AbstractScaleType { public static final ScaleType INSTANCE = new ScaleTypeFitXY(); @Override public void getTransformImpl( Matrix outTransform, Rect parentRect, int childWidth, int childHeight, float focusX, float focusY, float scaleX, float scaleY) { float dx = parentRect.left; float dy = parentRect.top; outTransform.setScale(scaleX, scaleY); outTransform.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f)); } @Override public String toString() { return "fit_xy"; } } private static class ScaleTypeFitStart extends AbstractScaleType { public static final ScaleType INSTANCE = new ScaleTypeFitStart(); @Override public void getTransformImpl( Matrix outTransform, Rect parentRect, int childWidth, int childHeight, float focusX, float focusY, float scaleX, float scaleY) { float scale = Math.min(scaleX, scaleY); float dx = parentRect.left; float dy = parentRect.top; outTransform.setScale(scale, scale); outTransform.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f)); } @Override public String toString() { return "fit_start"; } } private static class ScaleTypeFitCenter extends AbstractScaleType { public static final ScaleType INSTANCE = new ScaleTypeFitCenter(); @Override public void getTransformImpl( Matrix outTransform, Rect parentRect, int childWidth, int childHeight, float focusX, float focusY, float scaleX, float scaleY) { float scale = Math.min(scaleX, scaleY); float dx = parentRect.left + (parentRect.width() - childWidth * scale) * 0.5f; float dy = parentRect.top + (parentRect.height() - childHeight * scale) * 0.5f; outTransform.setScale(scale, scale); outTransform.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f)); } @Override public String toString() { return "fit_center"; } } private static class ScaleTypeFitEnd extends AbstractScaleType { public static final ScaleType INSTANCE = new ScaleTypeFitEnd(); @Override public void getTransformImpl( Matrix outTransform, Rect parentRect, int childWidth, int childHeight, float focusX, float focusY, float scaleX, float scaleY) { float scale = Math.min(scaleX, scaleY); float dx = parentRect.left + (parentRect.width() - childWidth * scale); float dy = parentRect.top + (parentRect.height() - childHeight * scale); outTransform.setScale(scale, scale); outTransform.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f)); } @Override public String toString() { return "fit_end"; } } private static class ScaleTypeCenter extends AbstractScaleType { public static final ScaleType INSTANCE = new ScaleTypeCenter(); @Override public void getTransformImpl( Matrix outTransform, Rect parentRect, int childWidth, int childHeight, float focusX, float focusY, float scaleX, float scaleY) { float dx = parentRect.left + (parentRect.width() - childWidth) * 0.5f; float dy = parentRect.top + (parentRect.height() - childHeight) * 0.5f; outTransform.setTranslate((int) (dx + 0.5f), (int) (dy + 0.5f)); } @Override public String toString() { return "center"; } } private static class ScaleTypeCenterInside extends AbstractScaleType { public static final ScaleType INSTANCE = new ScaleTypeCenterInside(); @Override public void getTransformImpl( Matrix outTransform, Rect parentRect, int childWidth, int childHeight, float focusX, float focusY, float scaleX, float scaleY) { float scale = Math.min(Math.min(scaleX, scaleY), 1.0f); float dx = parentRect.left + (parentRect.width() - childWidth * scale) * 0.5f; float dy = parentRect.top + (parentRect.height() - childHeight * scale) * 0.5f; outTransform.setScale(scale, scale); outTransform.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f)); } @Override public String toString() { return "center_inside"; } } private static class ScaleTypeCenterCrop extends AbstractScaleType { public static final ScaleType INSTANCE = new ScaleTypeCenterCrop(); @Override public void getTransformImpl( Matrix outTransform, Rect parentRect, int childWidth, int childHeight, float focusX, float focusY, float scaleX, float scaleY) { float scale, dx, dy; if (scaleY > scaleX) { scale = scaleY; dx = parentRect.left + (parentRect.width() - childWidth * scale) * 0.5f; dy = parentRect.top; } else { scale = scaleX; dx = parentRect.left; dy = parentRect.top + (parentRect.height() - childHeight * scale) * 0.5f; } outTransform.setScale(scale, scale); outTransform.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f)); } @Override public String toString() { return "center_crop"; } } private static class ScaleTypeFocusCrop extends AbstractScaleType { public static final ScaleType INSTANCE = new ScaleTypeFocusCrop(); @Override public void getTransformImpl( Matrix outTransform, Rect parentRect, int childWidth, int childHeight, float focusX, float focusY, float scaleX, float scaleY) { float scale, dx, dy; if (scaleY > scaleX) { scale = scaleY; dx = parentRect.width() * 0.5f - childWidth * scale * focusX; dx = parentRect.left + Math.max(Math.min(dx, 0), parentRect.width() - childWidth * scale); dy = parentRect.top; } else { scale = scaleX; dx = parentRect.left; dy = parentRect.height() * 0.5f - childHeight * scale * focusY; dy = parentRect.top + Math.max(Math.min(dy, 0), parentRect.height() - childHeight * scale); } outTransform.setScale(scale, scale); outTransform.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f)); } @Override public String toString() { return "focus_crop"; } } /** * Scaletypes that have some internal state and are not static. */ public interface StatefulScaleType { /** * Returns the internal state. The returned object must be immutable! * * The returned state may be used for caching the result of {@code ScaleType.getTransform}. * If null state is returned, the result will not be cached. If non-null state is returned, * the old transformation may be used if produced with an equal state. */ public Object getState(); } /** * Scale type that interpolates transform of the two underlying scale types. */ public static class InterpolatingScaleType implements ScaleType, StatefulScaleType { private final ScaleType mScaleTypeFrom; private final ScaleType mScaleTypeTo; private final @Nullable Rect mBoundsFrom; private final @Nullable Rect mBoundsTo; private final float[] mMatrixValuesFrom = new float[9]; private final float[] mMatrixValuesTo = new float[9]; private final float[] mMatrixValuesInterpolated = new float[9]; private float mInterpolatingValue; public InterpolatingScaleType( ScaleType scaleTypeFrom, ScaleType scaleTypeTo, @Nullable Rect boundsFrom, @Nullable Rect boundsTo) { mScaleTypeFrom = scaleTypeFrom; mScaleTypeTo = scaleTypeTo; mBoundsFrom = boundsFrom; mBoundsTo = boundsTo; } public InterpolatingScaleType(ScaleType scaleTypeFrom, ScaleType scaleTypeTo) { this(scaleTypeFrom, scaleTypeTo, null, null); } public ScaleType getScaleTypeFrom() { return mScaleTypeFrom; } public ScaleType getScaleTypeTo() { return mScaleTypeTo; } public @Nullable Rect getBoundsFrom() { return mBoundsFrom; } public @Nullable Rect getBoundsTo() { return mBoundsTo; } /** * Sets the interpolating value. * * Value of 0.0 will produce the transform same as ScaleTypeFrom. * Value of 1.0 will produce the transform same as ScaleTypeTo. * Inbetween values will produce a transform that is a linear combination between the two. */ public void setValue(float value) { mInterpolatingValue = value; } /** * Gets the interpolating value. */ public float getValue() { return mInterpolatingValue; } @Override public Object getState() { return mInterpolatingValue; } @Override public Matrix getTransform( Matrix transform, Rect parentBounds, int childWidth, int childHeight, float focusX, float focusY) { Rect boundsFrom = (mBoundsFrom != null) ? mBoundsFrom : parentBounds; Rect boundsTo = (mBoundsTo != null) ? mBoundsTo : parentBounds; mScaleTypeFrom.getTransform(transform, boundsFrom, childWidth, childHeight, focusX, focusY); transform.getValues(mMatrixValuesFrom); mScaleTypeTo.getTransform(transform, boundsTo, childWidth, childHeight, focusX, focusY); transform.getValues(mMatrixValuesTo); for (int i = 0; i < 9; i++) { mMatrixValuesInterpolated[i] = mMatrixValuesFrom[i] * (1 - mInterpolatingValue) + mMatrixValuesTo[i] * mInterpolatingValue; } transform.setValues(mMatrixValuesInterpolated); return transform; } @Override public String toString() { return String.format( "InterpolatingScaleType(%s -> %s)", String.valueOf(mScaleTypeFrom), String.valueOf(mScaleTypeTo)); } } }