com.mediatek.galleryfeature.stereo.segment.ImageShow.java Source code

Java tutorial

Introduction

Here is the source code for com.mediatek.galleryfeature.stereo.segment.ImageShow.java

Source

/* Copyright Statement:
*
* This software/firmware and related documentation ("MediaTek Software") are
* protected under relevant copyright laws. The information contained herein is
* confidential and proprietary to MediaTek Inc. and/or its licensors. Without
* the prior written permission of MediaTek inc. and/or its licensors, any
* reproduction, modification, use or disclosure of MediaTek Software, and
* information contained herein, in whole or in part, shall be strictly
* prohibited.
*
* MediaTek Inc. (C) 2015. All rights reserved.
*
* BY OPENING THIS FILE, RECEIVER HEREBY UNEQUIVOCALLY ACKNOWLEDGES AND AGREES
* THAT THE SOFTWARE/FIRMWARE AND ITS DOCUMENTATIONS ("MEDIATEK SOFTWARE")
* RECEIVED FROM MEDIATEK AND/OR ITS REPRESENTATIVES ARE PROVIDED TO RECEIVER
* ON AN "AS-IS" BASIS ONLY. MEDIATEK EXPRESSLY DISCLAIMS ANY AND ALL
* WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
* NONINFRINGEMENT. NEITHER DOES MEDIATEK PROVIDE ANY WARRANTY WHATSOEVER WITH
* RESPECT TO THE SOFTWARE OF ANY THIRD PARTY WHICH MAY BE USED BY,
* INCORPORATED IN, OR SUPPLIED WITH THE MEDIATEK SOFTWARE, AND RECEIVER AGREES
* TO LOOK ONLY TO SUCH THIRD PARTY FOR ANY WARRANTY CLAIM RELATING THERETO.
* RECEIVER EXPRESSLY ACKNOWLEDGES THAT IT IS RECEIVER'S SOLE RESPONSIBILITY TO
* OBTAIN FROM ANY THIRD PARTY ALL PROPER LICENSES CONTAINED IN MEDIATEK
* SOFTWARE. MEDIATEK SHALL ALSO NOT BE RESPONSIBLE FOR ANY MEDIATEK SOFTWARE
* RELEASES MADE TO RECEIVER'S SPECIFICATION OR TO CONFORM TO A PARTICULAR
* STANDARD OR OPEN FORUM. RECEIVER'S SOLE AND EXCLUSIVE REMEDY AND MEDIATEK'S
* ENTIRE AND CUMULATIVE LIABILITY WITH RESPECT TO THE MEDIATEK SOFTWARE
* RELEASED HEREUNDER WILL BE, AT MEDIATEK'S OPTION, TO REVISE OR REPLACE THE
* MEDIATEK SOFTWARE AT ISSUE, OR REFUND ANY SOFTWARE LICENSE FEES OR SERVICE
* CHARGE PAID BY RECEIVER TO MEDIATEK FOR SUCH MEDIATEK SOFTWARE AT ISSUE.
*
* The following software/firmware and/or related documentation ("MediaTek
* Software") have been modified by MediaTek Inc. All revisions are subject to
* any receiver's applicable license agreements with MediaTek Inc.
*/

package com.mediatek.galleryfeature.stereo.segment;

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.support.v4.widget.EdgeEffectCompat;
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 com.android.gallery3d.R;

import com.mediatek.galleryframework.util.MtkLog;

/**
 * A view specialized to to show a bitmap mastered by MainImageMaster.<br/>
 * It references a StereoSegmentWrapper to do segment operations as needed.
 * And It also provides common interfaces for gesture interaction.
 */
public class ImageShow extends View
        implements OnGestureListener, ScaleGestureDetector.OnScaleGestureListener, OnDoubleTapListener {
    private static final String LOGTAG = "MtkGallery2/SegmentApp/ImageShow";

    private static final boolean ENABLE_ZOOMED_COMPARISON = false;
    private static final int EDGE_LEFT = 1;
    private static final int EDGE_TOP = 2;
    private static final int EDGE_RIGHT = 3;
    private static final int EDGE_BOTTOM = 4;
    private static final int DEFAULT_EDGE_SIZE = 100;
    private static final int mAnimationSnapDelay = 200;
    private static final int mAnimationZoomDelay = 400;

    /**
     * Interaction mode enumeration.
     */
    private enum InteractionMode {
        NONE, SCALE, MOVE
    }

    protected boolean mIsZoomPanSupported;

    protected Paint mPaint = new Paint();
    protected MainImageMaster mMasterImage;
    protected StereoSegmentWrapper mMaskSimulator;
    protected Activity mActivity;

    private GestureDetector mGestureDetector;
    private ScaleGestureDetector mScaleGestureDetector;
    private Point mTouchDown = new Point();
    private Point mTouch = new Point();
    private Point mOriginalTranslation = new Point();
    private ValueAnimator mAnimatorScale;
    private ValueAnimator mAnimatorTranslateX;
    private ValueAnimator mAnimatorTranslateY;
    private EdgeEffectCompat mEdgeEffect;
    private InteractionMode mInteractionMode = InteractionMode.NONE;

    private boolean mZoomIn = false;
    private float mStartFocusX;
    private float mStartFocusY;
    private int mCurrentEdgeEffect = 0;
    private int mEdgeSize = DEFAULT_EDGE_SIZE;

    /**
     * Constructor.
     *
     * @param context
     *            The Context the view is running in, through which it can
     *            access the current theme, resources, etc.
     * @param attrs
     *            The attributes of the XML tag that is inflating the view.
     * @param defStyle
     *            An attribute in the current theme that contains a reference to
     *            a style resource that supplies default values for the view.
     *            Can be 0 to not look for defaults.
     */
    public ImageShow(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        setupImageShow(context);
    }

    /**
     * Constructor.
     *
     * @param context
     *            The Context the view is running in, through which it can
     *            access the current theme, resources, etc.
     * @param attrs
     *            The attributes of the XML tag that is inflating the view.
     */
    public ImageShow(Context context, AttributeSet attrs) {
        super(context, attrs);
        setupImageShow(context);
    }

    /**
     * Constructor.
     *
     * @param context
     *            The Context the view is running in, through which it can
     *            access the current theme, resources, etc.
     */
    public ImageShow(Context context) {
        super(context);
        setupImageShow(context);
    }

    /**
     * Set the MainImageMaster.
     * @param image the MainImageMaster.
     */
    public void setImageMaster(MainImageMaster image) {
        mMasterImage = image;
    }

    /**
     * Set the StereoSegmentWrapper.
     * @param segmenter the StereoSegmentWrapper.
     */
    public void setSegmenter(StereoSegmentWrapper segmenter) {
        mMaskSimulator = segmenter;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        int action = event.getAction();
        action = action & MotionEvent.ACTION_MASK;

        mGestureDetector.onTouchEvent(event);
        mScaleGestureDetector.onTouchEvent(event);
        if (mInteractionMode == InteractionMode.SCALE) {
            return 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;
            System.currentTimeMillis();
            mMasterImage.setOriginalTranslation(mMasterImage.getTranslation());
        }

        if (action == MotionEvent.ACTION_MOVE && mInteractionMode == InteractionMode.MOVE) {
            mTouch.x = ex;
            mTouch.y = ey;

            float scaleFactor = mMasterImage.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 = mMasterImage.getOriginalTranslation();
                Point translation = mMasterImage.getTranslation();
                translation.x = (int) (originalTranslation.x + translateX);
                translation.y = (int) (originalTranslation.y + translateY);
                mMasterImage.setTranslation(translation);
            }
        }

        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL
                || action == MotionEvent.ACTION_OUTSIDE) {
            mInteractionMode = InteractionMode.NONE;
            mTouchDown.x = 0;
            mTouchDown.y = 0;
            mTouch.x = 0;
            mTouch.y = 0;
            if (mMasterImage.getScaleFactor() <= 1) {
                mMasterImage.setScaleFactor(1);
                mMasterImage.resetTranslation();
            }
        }
        float scaleFactor = mMasterImage.getScaleFactor();
        Point translation = mMasterImage.getTranslation();
        constrainTranslation(translation, scaleFactor);
        mMasterImage.setTranslation(translation);

        invalidate();
        return true;
    }

    @Override
    public boolean onDoubleTap(MotionEvent event) {
        if (!mIsZoomPanSupported) {
            return false;
        }
        mZoomIn = !mZoomIn;
        float scale = 1.0f;
        final float x = event.getX();
        final float y = event.getY();
        if (mZoomIn) {
            scale = mMasterImage.getMaxScaleFactor();
        }
        if (scale != mMasterImage.getScaleFactor()) {
            if (mAnimatorScale != null) {
                mAnimatorScale.cancel();
            }
            mAnimatorScale = ValueAnimator.ofFloat(mMasterImage.getScaleFactor(), scale);
            float translateX = (getWidth() / 2 - x);
            float translateY = (getHeight() / 2 - y);
            Point translation = mMasterImage.getTranslation();
            int startTranslateX = translation.x;
            int startTranslateY = translation.y;
            if (scale != 1.0f) {
                translation.x = (int) (mOriginalTranslation.x + translateX);
                translation.y = (int) (mOriginalTranslation.y + translateY);
            } else {
                translation.x = 0;
                translation.y = 0;
            }
            constrainTranslation(translation, scale);
            startAnimTranslation(startTranslateX, translation.x, startTranslateY, translation.y,
                    mAnimationZoomDelay);
            mAnimatorScale.setDuration(mAnimationZoomDelay);
            mAnimatorScale.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    mMasterImage.setScaleFactor((Float) animation.getAnimatedValue());
                    invalidate();
                }
            });
            mAnimatorScale.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animation) {
                }

                @Override
                public void onAnimationEnd(Animator animation) {
                    applyTranslationConstraints();
                    invalidate();
                }

                @Override
                public void onAnimationCancel(Animator animation) {
                }

                @Override
                public void onAnimationRepeat(Animator animation) {
                }
            });
            mAnimatorScale.start();
        }
        return true;
    }

    @Override
    public boolean onDoubleTapEvent(MotionEvent arg0) {
        return false;
    }

    @Override
    public boolean onSingleTapConfirmed(MotionEvent arg0) {
        return false;
    }

    @Override
    public boolean onDown(MotionEvent arg0) {
        return false;
    }

    @Override
    public boolean onFling(MotionEvent startEvent, MotionEvent endEvent, float arg2, float arg3) {
        return false;
    }

    @Override
    public void onLongPress(MotionEvent arg0) {
    }

    @Override
    public boolean onScroll(MotionEvent arg0, MotionEvent arg1, float arg2, float arg3) {
        return false;
    }

    @Override
    public void onShowPress(MotionEvent arg0) {
    }

    @Override
    public boolean onSingleTapUp(MotionEvent event) {
        return false;
    }

    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        if (!mIsZoomPanSupported) {
            return false;
        }
        float scaleFactor = mMasterImage.getScaleFactor();

        scaleFactor = scaleFactor * detector.getScaleFactor();
        if (scaleFactor > mMasterImage.getMaxScaleFactor()) {
            scaleFactor = mMasterImage.getMaxScaleFactor();
        }
        if (scaleFactor < 1.0f) {
            scaleFactor = 1.0f;
        }
        mMasterImage.setScaleFactor(scaleFactor);
        scaleFactor = mMasterImage.getScaleFactor();
        float focusx = detector.getFocusX();
        float focusy = detector.getFocusY();
        float translateX = (focusx - mStartFocusX) / scaleFactor;
        float translateY = (focusy - mStartFocusY) / scaleFactor;
        Point translation = mMasterImage.getTranslation();
        translation.x = (int) (mOriginalTranslation.x + translateX);
        translation.y = (int) (mOriginalTranslation.y + translateY);
        mMasterImage.setTranslation(translation);
        invalidate();
        return true;
    }

    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {
        if (!mIsZoomPanSupported) {
            return false;
        }
        Point pos = mMasterImage.getTranslation();
        mOriginalTranslation.x = pos.x;
        mOriginalTranslation.y = pos.y;
        mMasterImage.getScaleFactor();
        mStartFocusX = detector.getFocusX();
        mStartFocusY = detector.getFocusY();
        mInteractionMode = InteractionMode.SCALE;
        return true;
    }

    @Override
    public void onScaleEnd(ScaleGestureDetector detector) {
        if (!mIsZoomPanSupported) {
            return;
        }
        mInteractionMode = InteractionMode.NONE;
        if (mMasterImage.getScaleFactor() < 1) {
            mMasterImage.setScaleFactor(1);
            invalidate();
        }
    }

    public void setZoomPanSupported(boolean isZoomPanSupported) {
        mIsZoomPanSupported = isZoomPanSupported;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
        int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
        setMeasuredDimension(parentWidth, parentHeight);
    }

    /**
     * This function calculates a screen to image Transformation matrix.
     *
     * @param reflectRotation
     *            set true if you want the rotation encoded.
     *            TODO useless, to be removed.
     * @return Screen to Image transformation matrix
     */
    protected Matrix getScreenToImageMatrix(boolean reflectRotation) {
        Rect originalBounds = mMasterImage.getOriginalBounds();
        int imgWidth = originalBounds.width();
        int imgHeight = originalBounds.height();

        Matrix m = mMasterImage.getImageToScreenMatrix(imgWidth, imgHeight, getWidth(), getHeight());
        Matrix invert = new Matrix();
        m.invert(invert);
        return invert;
    }

    // override this
    protected void doDraw(Canvas canvas) {
        Bitmap preview = mMasterImage.getBitmap();
        canvas.save();
        drawImages(canvas, preview);
        canvas.restore();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        mPaint.reset();
        mPaint.setAntiAlias(true);
        mPaint.setFilterBitmap(true);
        // skip when bitmap not ready
        if (mMasterImage.getOriginalBounds() == null) {
            MtkLog.v(LOGTAG, "<onDraw> bitmap not ready, skip this onDraw pass");
            return;
        }
        mMasterImage.setImageShowSize(getWidth(), getHeight());

        doDraw(canvas);
    }

    protected void drawImages(Canvas canvas, Bitmap... images) {
        Bitmap bitmap = images[0];
        if (bitmap == null) {
            return;
        }

        Matrix m = mMasterImage.getImageToScreenMatrix(bitmap.getWidth(), bitmap.getHeight(), getWidth(),
                getHeight());
        if (m == null) {
            return;
        }

        for (int i = 0; i < images.length; i++) {
            bitmap = images[i];
            if (bitmap != null) {
                canvas.drawBitmap(bitmap, m, mPaint);
            }
        }
    }

    protected void constrainTranslation(Point translation, float scale) {
        int currentEdgeEffect = 0;
        if (scale <= 1) {
            finishEdgeEffect();
            return;
        }
        Rect originalBounds = mMasterImage.getOriginalBounds();
        Matrix originalToScreen = mMasterImage.getImageToScreenMatrix(originalBounds.width(),
                originalBounds.height(), getWidth(), getHeight());
        if (originalToScreen == null) {
            finishEdgeEffect();
            return;
        }
        RectF screenPos = new RectF(originalBounds);
        originalToScreen.mapRect(screenPos);
        boolean rightConstraint = screenPos.right < getWidth();
        boolean leftConstraint = screenPos.left > 0;
        boolean topConstraint = screenPos.top > 0;
        boolean bottomConstraint = screenPos.bottom < getHeight();
        if (screenPos.width() > getWidth()) {
            if (rightConstraint && !leftConstraint) {
                float tx = screenPos.right - translation.x * scale;
                translation.x = (int) ((getWidth() - tx) / scale);
                currentEdgeEffect = EDGE_RIGHT;
            } else if (leftConstraint && !rightConstraint) {
                float tx = screenPos.left - translation.x * scale;
                translation.x = (int) ((-tx) / scale);
                currentEdgeEffect = EDGE_LEFT;
            }
        } else {
            float tx = screenPos.right - translation.x * scale;
            float dx = (getWidth() - screenPos.width()) / 2f;
            translation.x = (int) ((getWidth() - tx - dx) / scale);
        }
        if (screenPos.height() > getHeight()) {
            if (bottomConstraint && !topConstraint) {
                float ty = screenPos.bottom - translation.y * scale;
                translation.y = (int) ((getHeight() - ty) / scale);
                currentEdgeEffect = EDGE_BOTTOM;
            } else if (topConstraint && !bottomConstraint) {
                float ty = screenPos.top - translation.y * scale;
                translation.y = (int) ((-ty) / scale);
                currentEdgeEffect = EDGE_TOP;
            }
        } else {
            float ty = screenPos.bottom - translation.y * scale;
            float dy = (getHeight() - screenPos.height()) / 2f;
            translation.y = (int) ((getHeight() - ty - dy) / scale);
        }
        if (mCurrentEdgeEffect != currentEdgeEffect) {
            if (mCurrentEdgeEffect == 0 || currentEdgeEffect != 0) {
                mCurrentEdgeEffect = currentEdgeEffect;
                mEdgeEffect.finish();
            }
            mEdgeEffect.setSize(getWidth(), mEdgeSize);
        }
        if (currentEdgeEffect != 0) {
            mEdgeEffect.onPull(mEdgeSize);
        }
    }

    private void finishEdgeEffect() {
        mCurrentEdgeEffect = 0;
        mEdgeEffect.finish();
    }

    private void setupImageShow(Context context) {
        Resources res = context.getResources();
        res.getColor(R.color.background_screen);
        mGestureDetector = new GestureDetector(context, this);
        mScaleGestureDetector = new ScaleGestureDetector(context, this);
        mActivity = (Activity) context;
        mEdgeEffect = new EdgeEffectCompat(context);
        mEdgeSize = res.getDimensionPixelSize(R.dimen.edge_glow_size);
    }

    private void startAnimTranslation(int fromX, int toX, int fromY, int toY, int delay) {
        if (fromX == toX && fromY == toY) {
            return;
        }
        if (mAnimatorTranslateX != null) {
            mAnimatorTranslateX.cancel();
        }
        if (mAnimatorTranslateY != null) {
            mAnimatorTranslateY.cancel();
        }
        mAnimatorTranslateX = ValueAnimator.ofInt(fromX, toX);
        mAnimatorTranslateY = ValueAnimator.ofInt(fromY, toY);
        mAnimatorTranslateX.setDuration(delay);
        mAnimatorTranslateY.setDuration(delay);
        mAnimatorTranslateX.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                Point translation = mMasterImage.getTranslation();
                translation.x = (Integer) animation.getAnimatedValue();
                mMasterImage.setTranslation(translation);
                invalidate();
            }
        });
        mAnimatorTranslateY.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                Point translation = mMasterImage.getTranslation();
                translation.y = (Integer) animation.getAnimatedValue();
                mMasterImage.setTranslation(translation);
                invalidate();
            }
        });
        mAnimatorTranslateX.start();
        mAnimatorTranslateY.start();
    }

    private void applyTranslationConstraints() {
        float scaleFactor = mMasterImage.getScaleFactor();
        Point translation = mMasterImage.getTranslation();
        int x = translation.x;
        int y = translation.y;
        constrainTranslation(translation, scaleFactor);

        if (x != translation.x || y != translation.y) {
            startAnimTranslation(x, translation.x, y, translation.y, mAnimationSnapDelay);
        }
    }
}