/*
Copyright 2013-2015 David Morrissey
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.travis.library.widget.zoom;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.TypedArray;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build.VERSION;
import android.os.Handler;
import android.os.Message;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import com.travis.library.widget.R;
import com.travis.library.widget.zoom.decoder.CompatDecoderFactory;
import com.travis.library.widget.zoom.decoder.DecoderFactory;
import com.travis.library.widget.zoom.decoder.ImageDecoder;
import com.travis.library.widget.zoom.decoder.ImageRegionDecoder;
import com.travis.library.widget.zoom.decoder.SkiaImageDecoder;
import com.travis.library.widget.zoom.decoder.SkiaImageRegionDecoder;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.Executor;
/**
* Displays an image subsampled as necessary to avoid loading too much image data into memory. After a pinch to zoom in,
* a set of image tiles subsampled at higher resolution are loaded and displayed over the base layer. During pinch and
* zoom, tiles off screen or higher/lower resolution than required are discarded from memory.
* <p>
* Tiles are no larger than the max supported bitmap size, so with large images tiling may be used even when zoomed out.
* <p>
* v prefixes - coordinates, translations and distances measured in screen (view) pixels
* s prefixes - coordinates, translations and distances measured in source image pixels (scaled)
*/
public class ZoomImageView extends View {
private static final String TAG = ZoomImageView.class.getSimpleName();
/**
* Attempt to use EXIF information on the image to rotate it. Works for external files only.
*/
public static final int ORIENTATION_USE_EXIF = -1;
/**
* Display the image file in its native orientation.
*/
public static final int ORIENTATION_0 = 0;
/**
* Rotate the image 90 degrees clockwise.
*/
public static final int ORIENTATION_90 = 90;
/**
* Rotate the image 180 degrees.
*/
public static final int ORIENTATION_180 = 180;
/**
* Rotate the image 270 degrees clockwise.
*/
public static final int ORIENTATION_270 = 270;
private static final List<Integer> VALID_ORIENTATIONS = Arrays.asList(ORIENTATION_0, ORIENTATION_90, ORIENTATION_180, ORIENTATION_270, ORIENTATION_USE_EXIF);
/**
* During zoom animation, keep the point of the image that was tapped in the same place, and scale the image around it.
*/
public static final int ZOOM_FOCUS_FIXED = 1;
/**
* During zoom animation, move the point of the image that was tapped to the center of the screen.
*/
public static final int ZOOM_FOCUS_CENTER = 2;
/**
* Zoom in to and center the tapped point immediately without animating.
*/
public static final int ZOOM_FOCUS_CENTER_IMMEDIATE = 3;
private static final List<Integer> VALID_ZOOM_STYLES = Arrays.asList(ZOOM_FOCUS_FIXED, ZOOM_FOCUS_CENTER, ZOOM_FOCUS_CENTER_IMMEDIATE);
/**
* Quadratic ease out. Not recommended for scale animation, but good for panning.
*/
public static final int EASE_OUT_QUAD = 1;
/**
* Quadratic ease in and out.
*/
public static final int EASE_IN_OUT_QUAD = 2;
private static final List<Integer> VALID_EASING_STYLES = Arrays.asList(EASE_IN_OUT_QUAD, EASE_OUT_QUAD);
/**
* Don't allow the image to be panned off screen. As much of the image as possible is always displayed, centered in the view when it is smaller. This is the best option for galleries.
*/
public static final int PAN_LIMIT_INSIDE = 1;
/**
* Allows the image to be panned until it is just off screen, but no further. The edge of the image will stop when it is flush with the screen edge.
*/
public static final int PAN_LIMIT_OUTSIDE = 2;
/**
* Allows the image to be panned until a corner reaches the center of the screen but no further. Useful when you want to pan any spot on the image to the exact center of the screen.
*/
public static final int PAN_LIMIT_CENTER = 3;
private static final List<Integer> VALID_PAN_LIMITS = Arrays.asList(PAN_LIMIT_INSIDE, PAN_LIMIT_OUTSIDE, PAN_LIMIT_CENTER);
/**
* Scale the image so that both dimensions of the image will be equal to or less than the corresponding dimension of the view. The image is then centered in the view. This is the default behaviour and best for galleries.
*/
public static final int SCALE_TYPE_CENTER_INSIDE = 1;
/**
* Scale the image uniformly so that both dimensions of the image will be equal to or larger than the corresponding dimension of the view. The image is then centered in the view.
*/
public static final int SCALE_TYPE_CENTER_CROP = 2;
/**
* Scale the image so that both dimensions of the image will be equal to or less than the maxScale and equal to or larger than minScale. The image is then centered in the view.
*/
public static final int SCALE_TYPE_CUSTOM = 3;
private static final List<Integer> VALID_SCALE_TYPES = Arrays.asList(SCALE_TYPE_CENTER_CROP, SCALE_TYPE_CENTER_INSIDE, SCALE_TYPE_CUSTOM);
// Bitmap (preview or full image)
private Bitmap bitmap;
// Whether the bitmap is a preview image
private boolean bitmapIsPreview;
// Specifies if a cache handler is also referencing the bitmap. Do not recycle if so.
private boolean bitmapIsCached;
// Uri of full size image
private Uri uri;
// Sample size used to display the whole image when fully zoomed out
private int fullImageSampleSize;
// Map of zoom level to tile grid
private Map<Integer, List<Tile>> tileMap;
// Overlay tile boundaries and other info
private boolean debug;
// Image orientation setting
private int orientation = ORIENTATION_0;
// Max scale allowed (prevent infinite zoom)
private float maxScale = 2F;
// Min scale allowed (prevent infinite zoom)
private float minScale = minScale();
// Density to reach before loading higher resolution tiles
private int minimumTileDpi = -1;
// Pan limiting style
private int panLimit = PAN_LIMIT_INSIDE;
// Minimum scale type
private int minimumScaleType = SCALE_TYPE_CENTER_INSIDE;
// overrides for the dimensions of the generated tiles
public static int TILE_SIZE_AUTO = Integer.MAX_VALUE;
private int maxTileWidth = TILE_SIZE_AUTO;
private int maxTileHeight = TILE_SIZE_AUTO;
// Whether to use the