blob: b833cf85848a60821c913073ef10aa408f21280f [file] [log] [blame]
/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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.android.gallery3d.filtershow.imageshow;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.net.Uri;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.GestureDetector.OnDoubleTapListener;
import android.view.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.widget.LinearLayout;
import com.android.gallery3d.filtershow.FilterShowActivity;
import com.android.gallery3d.filtershow.cache.ImageLoader;
import com.android.gallery3d.filtershow.filters.ImageFilter;
import com.android.gallery3d.filtershow.presets.ImagePreset;
import java.io.File;
public class ImageShow extends View implements OnGestureListener,
ScaleGestureDetector.OnScaleGestureListener,
OnDoubleTapListener {
private static final String LOGTAG = "ImageShow";
private static final boolean ENABLE_ZOOMED_COMPARISON = false;
protected Paint mPaint = new Paint();
protected static int mTextSize = 24;
protected static int mTextPadding = 20;
protected ImageLoader mImageLoader = null;
private boolean mDirtyGeometry = false;
private Bitmap mBackgroundImage = null;
private final boolean USE_BACKGROUND_IMAGE = false;
private static int mBackgroundColor = Color.RED;
private GestureDetector mGestureDetector = null;
private ScaleGestureDetector mScaleGestureDetector = null;
protected Rect mImageBounds = new Rect();
private boolean mOriginalDisabled = false;
private boolean mTouchShowOriginal = false;
private long mTouchShowOriginalDate = 0;
private final long mTouchShowOriginalDelayMin = 200; // 200ms
private final long mTouchShowOriginalDelayMax = 300; // 300ms
private int mShowOriginalDirection = 0;
private static int UNVEIL_HORIZONTAL = 1;
private static int UNVEIL_VERTICAL = 2;
private Point mTouchDown = new Point();
private Point mTouch = new Point();
private boolean mFinishedScalingOperation = false;
private static int mOriginalTextMargin = 8;
private static int mOriginalTextSize = 26;
private static String mOriginalText = "Original";
private boolean mZoomIn = false;
Point mOriginalTranslation = new Point();
float mOriginalScale;
float mStartFocusX, mStartFocusY;
private enum InteractionMode {
NONE,
SCALE,
MOVE
}
private String mToast = null;
private boolean mShowToast = false;
private boolean mImportantToast = false;
InteractionMode mInteractionMode = InteractionMode.NONE;
protected GeometryMetadata getGeometry() {
return new GeometryMetadata(getImagePreset().mGeoData);
}
private FilterShowActivity mActivity = null;
public static void setDefaultBackgroundColor(int value) {
mBackgroundColor = value;
}
public FilterShowActivity getActivity() {
return mActivity;
}
public int getDefaultBackgroundColor() {
return mBackgroundColor;
}
public static void setTextSize(int value) {
mTextSize = value;
}
public static void setTextPadding(int value) {
mTextPadding = value;
}
public static void setOriginalTextMargin(int value) {
mOriginalTextMargin = value;
}
public static void setOriginalTextSize(int value) {
mOriginalTextSize = value;
}
public static void setOriginalText(String text) {
mOriginalText = text;
}
private final Handler mHandler = new Handler();
public void select() {
}
public void unselect() {
}
public boolean hasModifications() {
if (getImagePreset() == null) {
return false;
}
return getImagePreset().hasModifications();
}
public void resetParameter() {
// TODO: implement reset
}
public void onNewValue(int parameter) {
invalidate();
mActivity.enableSave(hasModifications());
}
public Point getTouchPoint() {
return mTouch;
}
public ImageShow(Context context, AttributeSet attrs) {
super(context, attrs);
setupGestureDetector(context);
mActivity = (FilterShowActivity) context;
MasterImage.getImage().addObserver(this);
}
public ImageShow(Context context) {
super(context);
setupGestureDetector(context);
mActivity = (FilterShowActivity) context;
MasterImage.getImage().addObserver(this);
}
public void setupGestureDetector(Context context) {
mGestureDetector = new GestureDetector(context, this);
mScaleGestureDetector = new ScaleGestureDetector(context, this);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(parentWidth, parentHeight);
}
public ImageFilter getCurrentFilter() {
return MasterImage.getImage().getCurrentFilter();
}
public void showToast(String text) {
showToast(text, false);
}
public void showToast(String text, boolean important) {
mToast = text;
mShowToast = true;
mImportantToast = important;
invalidate();
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mShowToast = false;
invalidate();
}
}, 400);
}
public Rect getImageBounds() {
Rect dst = new Rect();
getImagePreset().mGeoData.getPhotoBounds().roundOut(dst);
return dst;
}
public Rect getImageCropBounds() {
return GeometryMath.roundNearest(getImagePreset().mGeoData.getPreviewCropBounds());
}
/* consider moving the following 2 methods into a subclass */
/**
* This function calculates a Image to Screen Transformation matrix
*
* @param reflectRotation set true if you want the rotation encoded
* @return Image to Screen transformation matrix
*/
protected Matrix getImageToScreenMatrix(boolean reflectRotation) {
GeometryMetadata geo = getImagePreset().mGeoData;
if (geo == null || mImageLoader == null
|| mImageLoader.getOriginalBounds() == null) {
return new Matrix();
}
Matrix m = geo.getOriginalToScreen(reflectRotation,
mImageLoader.getOriginalBounds().width(),
mImageLoader.getOriginalBounds().height(), getWidth(), getHeight());
Point translate = MasterImage.getImage().getTranslation();
float scaleFactor = MasterImage.getImage().getScaleFactor();
m.postTranslate(translate.x, translate.y);
m.postScale(scaleFactor, scaleFactor, getWidth() / 2.0f, getHeight() / 2.0f);
return m;
}
/**
* This function calculates a to Screen Image Transformation matrix
*
* @param reflectRotation set true if you want the rotation encoded
* @return Screen to Image transformation matrix
*/
protected Matrix getScreenToImageMatrix(boolean reflectRotation) {
Matrix m = getImageToScreenMatrix(reflectRotation);
Matrix invert = new Matrix();
m.invert(invert);
return invert;
}
public Rect getDisplayedImageBounds() {
return mImageBounds;
}
public ImagePreset getImagePreset() {
return MasterImage.getImage().getPreset();
}
public void drawToast(Canvas canvas) {
if (mShowToast && mToast != null) {
Paint paint = new Paint();
paint.setTextSize(128);
float textWidth = paint.measureText(mToast);
int toastX = (int) ((getWidth() - textWidth) / 2.0f);
int toastY = (int) (getHeight() / 3.0f);
paint.setARGB(255, 0, 0, 0);
canvas.drawText(mToast, toastX - 2, toastY - 2, paint);
canvas.drawText(mToast, toastX - 2, toastY, paint);
canvas.drawText(mToast, toastX, toastY - 2, paint);
canvas.drawText(mToast, toastX + 2, toastY + 2, paint);
canvas.drawText(mToast, toastX + 2, toastY, paint);
canvas.drawText(mToast, toastX, toastY + 2, paint);
if (mImportantToast) {
paint.setARGB(255, 200, 0, 0);
} else {
paint.setARGB(255, 255, 255, 255);
}
canvas.drawText(mToast, toastX, toastY, paint);
}
}
@Override
public void onDraw(Canvas canvas) {
MasterImage.getImage().setImageShowSize(getWidth(), getHeight());
float cx = canvas.getWidth()/2.0f;
float cy = canvas.getHeight()/2.0f;
float scaleFactor = MasterImage.getImage().getScaleFactor();
Point translation = MasterImage.getImage().getTranslation();
Matrix scalingMatrix = new Matrix();
scalingMatrix.postScale(scaleFactor, scaleFactor, cx, cy);
scalingMatrix.preTranslate(translation.x, translation.y);
RectF unscaledClipRect = new RectF(mImageBounds);
scalingMatrix.mapRect(unscaledClipRect, unscaledClipRect);
canvas.save();
boolean enablePartialRendering = false;
// For now, partial rendering is disabled for all filters,
// so no need to clip.
if (enablePartialRendering && !unscaledClipRect.isEmpty()) {
canvas.clipRect(unscaledClipRect);
}
canvas.save();
// TODO: center scale on gesture
canvas.scale(scaleFactor, scaleFactor, cx, cy);
canvas.translate(translation.x, translation.y);
drawBackground(canvas);
drawImage(canvas, getFilteredImage(), true);
Bitmap highresPreview = MasterImage.getImage().getHighresImage();
if (highresPreview != null) {
drawImage(canvas, highresPreview, false);
}
canvas.restore();
if (showTitle() && getImagePreset() != null) {
mPaint.setARGB(200, 0, 0, 0);
mPaint.setTextSize(mTextSize);
Rect textRect = new Rect(0, 0, getWidth(), mTextSize + mTextPadding);
canvas.drawRect(textRect, mPaint);
mPaint.setARGB(255, 200, 200, 200);
canvas.drawText(getImagePreset().name(), mTextPadding,
1.5f * mTextPadding, mPaint);
}
Bitmap partialPreview = MasterImage.getImage().getPartialImage();
if (partialPreview != null) {
Rect src = new Rect(0, 0, partialPreview.getWidth(), partialPreview.getHeight());
Rect dest = new Rect(0, 0, getWidth(), getHeight());
canvas.drawBitmap(partialPreview, src, dest, mPaint);
}
canvas.save();
canvas.scale(scaleFactor, scaleFactor, cx, cy);
canvas.translate(translation.x, translation.y);
drawPartialImage(canvas, getGeometryOnlyImage());
canvas.restore();
canvas.restore();
drawToast(canvas);
}
public void resetImageCaches(ImageShow caller) {
if (mImageLoader == null) {
return;
}
MasterImage.getImage().updatePresets(true);
}
public Bitmap getFiltersOnlyImage() {
return MasterImage.getImage().getFiltersOnlyImage();
}
public Bitmap getGeometryOnlyImage() {
return MasterImage.getImage().getGeometryOnlyImage();
}
public Bitmap getFilteredImage() {
return MasterImage.getImage().getFilteredImage();
}
public void drawImage(Canvas canvas, Bitmap image, boolean updateBounds) {
if (image != null) {
Rect s = new Rect(0, 0, image.getWidth(),
image.getHeight());
float scale = GeometryMath.scale(image.getWidth(), image.getHeight(), getWidth(),
getHeight());
float w = image.getWidth() * scale;
float h = image.getHeight() * scale;
float ty = (getHeight() - h) / 2.0f;
float tx = (getWidth() - w) / 2.0f;
Rect d = new Rect((int) tx, (int) ty, (int) (w + tx),
(int) (h + ty));
if (updateBounds) {
mImageBounds = d;
}
canvas.drawBitmap(image, s, d, mPaint);
}
}
public void drawPartialImage(Canvas canvas, Bitmap image) {
boolean showsOriginal = MasterImage.getImage().showsOriginal();
if (!showsOriginal && !mTouchShowOriginal)
return;
canvas.save();
if (image != null) {
if (mShowOriginalDirection == 0) {
if (Math.abs(mTouch.y - mTouchDown.y) > Math.abs(mTouch.x - mTouchDown.x)) {
mShowOriginalDirection = UNVEIL_VERTICAL;
} else {
mShowOriginalDirection = UNVEIL_HORIZONTAL;
}
}
int px = 0;
int py = 0;
if (mShowOriginalDirection == UNVEIL_VERTICAL) {
px = mImageBounds.width();
py = (int) (mTouch.y - mImageBounds.top);
} else {
px = (int) (mTouch.x - mImageBounds.left);
py = mImageBounds.height();
if (showsOriginal) {
px = mImageBounds.width();
}
}
Rect d = new Rect(mImageBounds.left, mImageBounds.top,
mImageBounds.left + px, mImageBounds.top + py);
canvas.clipRect(d);
drawImage(canvas, image, false);
Paint paint = new Paint();
paint.setColor(Color.BLACK);
paint.setStrokeWidth(3);
if (mShowOriginalDirection == UNVEIL_VERTICAL) {
canvas.drawLine(mImageBounds.left, mTouch.y,
mImageBounds.right, mTouch.y, paint);
} else {
canvas.drawLine(mTouch.x, mImageBounds.top,
mTouch.x, mImageBounds.bottom, paint);
}
Rect bounds = new Rect();
paint.setAntiAlias(true);
paint.setTextSize(mOriginalTextSize);
paint.getTextBounds(mOriginalText, 0, mOriginalText.length(), bounds);
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(3);
canvas.drawText(mOriginalText, mImageBounds.left + mOriginalTextMargin,
mImageBounds.top + bounds.height() + mOriginalTextMargin, paint);
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(1);
paint.setColor(Color.WHITE);
canvas.drawText(mOriginalText, mImageBounds.left + mOriginalTextMargin,
mImageBounds.top + bounds.height() + mOriginalTextMargin, paint);
}
canvas.restore();
}
public void drawBackground(Canvas canvas) {
if (USE_BACKGROUND_IMAGE) {
if (mBackgroundImage == null) {
mBackgroundImage = mImageLoader.getBackgroundBitmap(getResources());
}
if (mBackgroundImage != null) {
Rect s = new Rect(0, 0, mBackgroundImage.getWidth(),
mBackgroundImage.getHeight());
Rect d = new Rect(0, 0, getWidth(), getHeight());
canvas.drawBitmap(mBackgroundImage, s, d, mPaint);
}
} else {
canvas.drawARGB(0, 0, 0, 0);
}
}
public boolean showTitle() {
return false;
}
public void setImageLoader(ImageLoader loader) {
mImageLoader = loader;
if (mImageLoader != null) {
mImageLoader.addListener(this);
MasterImage.getImage().setImageLoader(mImageLoader);
}
}
private void setDirtyGeometryFlag() {
mDirtyGeometry = true;
}
protected void clearDirtyGeometryFlag() {
mDirtyGeometry = false;
}
protected boolean getDirtyGeometryFlag() {
return mDirtyGeometry;
}
private void imageSizeChanged(Bitmap image) {
if (image == null || getImagePreset() == null)
return;
float w = image.getWidth();
float h = image.getHeight();
GeometryMetadata geo = getImagePreset().mGeoData;
RectF pb = geo.getPhotoBounds();
if (w == pb.width() && h == pb.height()) {
return;
}
RectF r = new RectF(0, 0, w, h);
getImagePreset().mGeoData.setPhotoBounds(r);
getImagePreset().mGeoData.setCropBounds(r);
}
public boolean updateGeometryFlags() {
return true;
}
public void updateImage() {
invalidate();
if (!updateGeometryFlags()) {
return;
}
Bitmap bitmap = mImageLoader.getOriginalBitmapLarge();
if (bitmap != null) {
imageSizeChanged(bitmap);
}
}
public void imageLoaded() {
updateImage();
invalidate();
}
public void saveImage(FilterShowActivity filterShowActivity, File file) {
mImageLoader.saveImage(getImagePreset(), filterShowActivity, file);
}
public void saveToUri(Bitmap f, Uri u, String m, FilterShowActivity filterShowActivity) {
mImageLoader.saveToUri(f, u, m, filterShowActivity);
}
public void returnFilteredResult(FilterShowActivity filterShowActivity) {
mImageLoader.returnFilteredResult(getImagePreset(), filterShowActivity);
}
public boolean scaleInProgress() {
return mScaleGestureDetector.isInProgress();
}
protected boolean isOriginalDisabled() {
return mOriginalDisabled;
}
protected void setOriginalDisabled(boolean originalDisabled) {
mOriginalDisabled = originalDisabled;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
int action = event.getAction();
action = action & MotionEvent.ACTION_MASK;
mGestureDetector.onTouchEvent(event);
boolean scaleInProgress = scaleInProgress();
mScaleGestureDetector.onTouchEvent(event);
if (mInteractionMode == InteractionMode.SCALE) {
return true;
}
if (!scaleInProgress() && scaleInProgress) {
// If we were scaling, the scale will stop but we will
// still issue an ACTION_UP. Let the subclasses know.
mFinishedScalingOperation = true;
}
int ex = (int) event.getX();
int ey = (int) event.getY();
if (action == MotionEvent.ACTION_DOWN) {
mInteractionMode = InteractionMode.MOVE;
mTouchDown.x = ex;
mTouchDown.y = ey;
mTouchShowOriginalDate = System.currentTimeMillis();
mShowOriginalDirection = 0;
MasterImage.getImage().setOriginalTranslation(MasterImage.getImage().getTranslation());
}
if (action == MotionEvent.ACTION_MOVE && mInteractionMode == InteractionMode.MOVE) {
mTouch.x = ex;
mTouch.y = ey;
float scaleFactor = MasterImage.getImage().getScaleFactor();
if (scaleFactor > 1 && (!ENABLE_ZOOMED_COMPARISON || event.getPointerCount() == 2)) {
float translateX = (mTouch.x - mTouchDown.x) / scaleFactor;
float translateY = (mTouch.y - mTouchDown.y) / scaleFactor;
Point originalTranslation = MasterImage.getImage().getOriginalTranslation();
Point translation = MasterImage.getImage().getTranslation();
translation.x = (int) (originalTranslation.x + translateX);
translation.y = (int) (originalTranslation.y + translateY);
MasterImage.getImage().setTranslation(translation);
mTouchShowOriginal = false;
} else if (enableComparison() && !mOriginalDisabled
&& (System.currentTimeMillis() - mTouchShowOriginalDate
> mTouchShowOriginalDelayMin)
&& event.getPointerCount() == 1) {
mTouchShowOriginal = true;
}
}
if (action == MotionEvent.ACTION_UP) {
mInteractionMode = InteractionMode.NONE;
mTouchShowOriginal = false;
mTouchDown.x = 0;
mTouchDown.y = 0;
mTouch.x = 0;
mTouch.y = 0;
if (MasterImage.getImage().getScaleFactor() <= 1) {
MasterImage.getImage().setScaleFactor(1);
MasterImage.getImage().resetTranslation();
}
}
invalidate();
return true;
}
protected boolean enableComparison() {
return true;
}
// listview stuff
public void showOriginal(boolean show) {
invalidate();
}
@Override
public boolean onDoubleTap(MotionEvent arg0) {
mZoomIn = !mZoomIn;
float scale = 1.0f;
if (mZoomIn) {
scale = MasterImage.getImage().getMaxScaleFactor();
}
if (scale != MasterImage.getImage().getScaleFactor()) {
MasterImage.getImage().setScaleFactor(scale);
invalidate();
}
return true;
}
@Override
public boolean onDoubleTapEvent(MotionEvent arg0) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent arg0) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean onDown(MotionEvent arg0) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean onFling(MotionEvent startEvent, MotionEvent endEvent, float arg2, float arg3) {
if (mActivity == null) {
return false;
}
if (endEvent.getPointerCount() == 2) {
return false;
}
return true;
}
@Override
public void onLongPress(MotionEvent arg0) {
// TODO Auto-generated method stub
}
@Override
public boolean onScroll(MotionEvent arg0, MotionEvent arg1, float arg2, float arg3) {
// TODO Auto-generated method stub
return false;
}
@Override
public void onShowPress(MotionEvent arg0) {
// TODO Auto-generated method stub
}
@Override
public boolean onSingleTapUp(MotionEvent arg0) {
// TODO Auto-generated method stub
return false;
}
public boolean useUtilityPanel() {
return false;
}
public void openUtilityPanel(final LinearLayout accessoryViewList) {
// TODO Auto-generated method stub
}
@Override
public boolean onScale(ScaleGestureDetector detector) {
MasterImage img = MasterImage.getImage();
float scaleFactor = img.getScaleFactor();
Point pos = img.getTranslation();
scaleFactor = scaleFactor * detector.getScaleFactor();
if (scaleFactor > MasterImage.getImage().getMaxScaleFactor()) {
scaleFactor = MasterImage.getImage().getMaxScaleFactor();
}
if (scaleFactor < 0.5) {
scaleFactor = 0.5f;
}
MasterImage.getImage().setScaleFactor(scaleFactor);
scaleFactor = img.getScaleFactor();
pos = img.getTranslation();
float focusx = detector.getFocusX();
float focusy = detector.getFocusY();
float translateX = (focusx - mStartFocusX) / scaleFactor;
float translateY = (focusy - mStartFocusY) / scaleFactor;
Point translation = MasterImage.getImage().getTranslation();
translation.x = (int) (mOriginalTranslation.x + translateX);
translation.y = (int) (mOriginalTranslation.y + translateY);
MasterImage.getImage().setTranslation(translation);
invalidate();
return true;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
Point pos = MasterImage.getImage().getTranslation();
mOriginalTranslation.x = pos.x;
mOriginalTranslation.y = pos.y;
mOriginalScale = MasterImage.getImage().getScaleFactor();
mStartFocusX = detector.getFocusX();
mStartFocusY = detector.getFocusY();
mInteractionMode = InteractionMode.SCALE;
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
mInteractionMode = InteractionMode.NONE;
if (MasterImage.getImage().getScaleFactor() < 1) {
MasterImage.getImage().setScaleFactor(1);
invalidate();
}
}
public boolean didFinishScalingOperation() {
if (mFinishedScalingOperation) {
mFinishedScalingOperation = false;
return true;
}
return false;
}
}