michael@0: /* michael@0: * Copyright (C) 2013 Square, Inc. michael@0: * michael@0: * Licensed under the Apache License, Version 2.0 (the "License"); michael@0: * you may not use this file except in compliance with the License. michael@0: * You may obtain a copy of the License at michael@0: * michael@0: * http://www.apache.org/licenses/LICENSE-2.0 michael@0: * michael@0: * Unless required by applicable law or agreed to in writing, software michael@0: * distributed under the License is distributed on an "AS IS" BASIS, michael@0: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. michael@0: * See the License for the specific language governing permissions and michael@0: * limitations under the License. michael@0: */ michael@0: package com.squareup.picasso; michael@0: michael@0: import android.net.Uri; michael@0: import java.util.ArrayList; michael@0: import java.util.List; michael@0: michael@0: import static java.util.Collections.unmodifiableList; michael@0: michael@0: /** Immutable data about an image and the transformations that will be applied to it. */ michael@0: public final class Request { michael@0: /** michael@0: * The image URI. michael@0: *

michael@0: * This is mutually exclusive with {@link #resourceId}. michael@0: */ michael@0: public final Uri uri; michael@0: /** michael@0: * The image resource ID. michael@0: *

michael@0: * This is mutually exclusive with {@link #uri}. michael@0: */ michael@0: public final int resourceId; michael@0: /** List of custom transformations to be applied after the built-in transformations. */ michael@0: public final List transformations; michael@0: /** Target image width for resizing. */ michael@0: public final int targetWidth; michael@0: /** Target image height for resizing. */ michael@0: public final int targetHeight; michael@0: /** michael@0: * True if the final image should use the 'centerCrop' scale technique. michael@0: *

michael@0: * This is mutually exclusive with {@link #centerInside}. michael@0: */ michael@0: public final boolean centerCrop; michael@0: /** michael@0: * True if the final image should use the 'centerInside' scale technique. michael@0: *

michael@0: * This is mutually exclusive with {@link #centerCrop}. michael@0: */ michael@0: public final boolean centerInside; michael@0: /** Amount to rotate the image in degrees. */ michael@0: public final float rotationDegrees; michael@0: /** Rotation pivot on the X axis. */ michael@0: public final float rotationPivotX; michael@0: /** Rotation pivot on the Y axis. */ michael@0: public final float rotationPivotY; michael@0: /** Whether or not {@link #rotationPivotX} and {@link #rotationPivotY} are set. */ michael@0: public final boolean hasRotationPivot; michael@0: michael@0: private Request(Uri uri, int resourceId, List transformations, int targetWidth, michael@0: int targetHeight, boolean centerCrop, boolean centerInside, float rotationDegrees, michael@0: float rotationPivotX, float rotationPivotY, boolean hasRotationPivot) { michael@0: this.uri = uri; michael@0: this.resourceId = resourceId; michael@0: if (transformations == null) { michael@0: this.transformations = null; michael@0: } else { michael@0: this.transformations = unmodifiableList(transformations); michael@0: } michael@0: this.targetWidth = targetWidth; michael@0: this.targetHeight = targetHeight; michael@0: this.centerCrop = centerCrop; michael@0: this.centerInside = centerInside; michael@0: this.rotationDegrees = rotationDegrees; michael@0: this.rotationPivotX = rotationPivotX; michael@0: this.rotationPivotY = rotationPivotY; michael@0: this.hasRotationPivot = hasRotationPivot; michael@0: } michael@0: michael@0: String getName() { michael@0: if (uri != null) { michael@0: return uri.getPath(); michael@0: } michael@0: return Integer.toHexString(resourceId); michael@0: } michael@0: michael@0: public boolean hasSize() { michael@0: return targetWidth != 0; michael@0: } michael@0: michael@0: boolean needsTransformation() { michael@0: return needsMatrixTransform() || hasCustomTransformations(); michael@0: } michael@0: michael@0: boolean needsMatrixTransform() { michael@0: return targetWidth != 0 || rotationDegrees != 0; michael@0: } michael@0: michael@0: boolean hasCustomTransformations() { michael@0: return transformations != null; michael@0: } michael@0: michael@0: public Builder buildUpon() { michael@0: return new Builder(this); michael@0: } michael@0: michael@0: /** Builder for creating {@link Request} instances. */ michael@0: public static final class Builder { michael@0: private Uri uri; michael@0: private int resourceId; michael@0: private int targetWidth; michael@0: private int targetHeight; michael@0: private boolean centerCrop; michael@0: private boolean centerInside; michael@0: private float rotationDegrees; michael@0: private float rotationPivotX; michael@0: private float rotationPivotY; michael@0: private boolean hasRotationPivot; michael@0: private List transformations; michael@0: michael@0: /** Start building a request using the specified {@link Uri}. */ michael@0: public Builder(Uri uri) { michael@0: setUri(uri); michael@0: } michael@0: michael@0: /** Start building a request using the specified resource ID. */ michael@0: public Builder(int resourceId) { michael@0: setResourceId(resourceId); michael@0: } michael@0: michael@0: Builder(Uri uri, int resourceId) { michael@0: this.uri = uri; michael@0: this.resourceId = resourceId; michael@0: } michael@0: michael@0: private Builder(Request request) { michael@0: uri = request.uri; michael@0: resourceId = request.resourceId; michael@0: targetWidth = request.targetWidth; michael@0: targetHeight = request.targetHeight; michael@0: centerCrop = request.centerCrop; michael@0: centerInside = request.centerInside; michael@0: rotationDegrees = request.rotationDegrees; michael@0: rotationPivotX = request.rotationPivotX; michael@0: rotationPivotY = request.rotationPivotY; michael@0: hasRotationPivot = request.hasRotationPivot; michael@0: if (request.transformations != null) { michael@0: transformations = new ArrayList(request.transformations); michael@0: } michael@0: } michael@0: michael@0: boolean hasImage() { michael@0: return uri != null || resourceId != 0; michael@0: } michael@0: michael@0: boolean hasSize() { michael@0: return targetWidth != 0; michael@0: } michael@0: michael@0: /** michael@0: * Set the target image Uri. michael@0: *

michael@0: * This will clear an image resource ID if one is set. michael@0: */ michael@0: public Builder setUri(Uri uri) { michael@0: if (uri == null) { michael@0: throw new IllegalArgumentException("Image URI may not be null."); michael@0: } michael@0: this.uri = uri; michael@0: this.resourceId = 0; michael@0: return this; michael@0: } michael@0: michael@0: /** michael@0: * Set the target image resource ID. michael@0: *

michael@0: * This will clear an image Uri if one is set. michael@0: */ michael@0: public Builder setResourceId(int resourceId) { michael@0: if (resourceId == 0) { michael@0: throw new IllegalArgumentException("Image resource ID may not be 0."); michael@0: } michael@0: this.resourceId = resourceId; michael@0: this.uri = null; michael@0: return this; michael@0: } michael@0: michael@0: /** Resize the image to the specified size in pixels. */ michael@0: public Builder resize(int targetWidth, int targetHeight) { michael@0: if (targetWidth <= 0) { michael@0: throw new IllegalArgumentException("Width must be positive number."); michael@0: } michael@0: if (targetHeight <= 0) { michael@0: throw new IllegalArgumentException("Height must be positive number."); michael@0: } michael@0: this.targetWidth = targetWidth; michael@0: this.targetHeight = targetHeight; michael@0: return this; michael@0: } michael@0: michael@0: /** Clear the resize transformation, if any. This will also clear center crop/inside if set. */ michael@0: public Builder clearResize() { michael@0: targetWidth = 0; michael@0: targetHeight = 0; michael@0: centerCrop = false; michael@0: centerInside = false; michael@0: return this; michael@0: } michael@0: michael@0: /** michael@0: * Crops an image inside of the bounds specified by {@link #resize(int, int)} rather than michael@0: * distorting the aspect ratio. This cropping technique scales the image so that it fills the michael@0: * requested bounds and then crops the extra. michael@0: */ michael@0: public Builder centerCrop() { michael@0: if (centerInside) { michael@0: throw new IllegalStateException("Center crop can not be used after calling centerInside"); michael@0: } michael@0: centerCrop = true; michael@0: return this; michael@0: } michael@0: michael@0: /** Clear the center crop transformation flag, if set. */ michael@0: public Builder clearCenterCrop() { michael@0: centerCrop = false; michael@0: return this; michael@0: } michael@0: michael@0: /** michael@0: * Centers an image inside of the bounds specified by {@link #resize(int, int)}. This scales michael@0: * the image so that both dimensions are equal to or less than the requested bounds. michael@0: */ michael@0: public Builder centerInside() { michael@0: if (centerCrop) { michael@0: throw new IllegalStateException("Center inside can not be used after calling centerCrop"); michael@0: } michael@0: centerInside = true; michael@0: return this; michael@0: } michael@0: michael@0: /** Clear the center inside transformation flag, if set. */ michael@0: public Builder clearCenterInside() { michael@0: centerInside = false; michael@0: return this; michael@0: } michael@0: michael@0: /** Rotate the image by the specified degrees. */ michael@0: public Builder rotate(float degrees) { michael@0: rotationDegrees = degrees; michael@0: return this; michael@0: } michael@0: michael@0: /** Rotate the image by the specified degrees around a pivot point. */ michael@0: public Builder rotate(float degrees, float pivotX, float pivotY) { michael@0: rotationDegrees = degrees; michael@0: rotationPivotX = pivotX; michael@0: rotationPivotY = pivotY; michael@0: hasRotationPivot = true; michael@0: return this; michael@0: } michael@0: michael@0: /** Clear the rotation transformation, if any. */ michael@0: public Builder clearRotation() { michael@0: rotationDegrees = 0; michael@0: rotationPivotX = 0; michael@0: rotationPivotY = 0; michael@0: hasRotationPivot = false; michael@0: return this; michael@0: } michael@0: michael@0: /** michael@0: * Add a custom transformation to be applied to the image. michael@0: *

michael@0: * Custom transformations will always be run after the built-in transformations. michael@0: */ michael@0: public Builder transform(Transformation transformation) { michael@0: if (transformation == null) { michael@0: throw new IllegalArgumentException("Transformation must not be null."); michael@0: } michael@0: if (transformations == null) { michael@0: transformations = new ArrayList(2); michael@0: } michael@0: transformations.add(transformation); michael@0: return this; michael@0: } michael@0: michael@0: /** Create the immutable {@link Request} object. */ michael@0: public Request build() { michael@0: if (centerInside && centerCrop) { michael@0: throw new IllegalStateException("Center crop and center inside can not be used together."); michael@0: } michael@0: if (centerCrop && targetWidth == 0) { michael@0: throw new IllegalStateException("Center crop requires calling resize."); michael@0: } michael@0: if (centerInside && targetWidth == 0) { michael@0: throw new IllegalStateException("Center inside requires calling resize."); michael@0: } michael@0: return new Request(uri, resourceId, transformations, targetWidth, targetHeight, centerCrop, michael@0: centerInside, rotationDegrees, rotationPivotX, rotationPivotY, hasRotationPivot); michael@0: } michael@0: } michael@0: }