blob: 43193acabed04d0e755652c1d436a16fcfa85cec [file] [log] [blame]
/*
* Copyright (c) 2016, The Linux Foundation. All rights reserved.
* Not a Contribution.
*
* 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 java.util.Arrays;
import java.util.Stack;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.LightingColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import com.android.gallery3d.filtershow.editors.EditorTruePortraitMask;
import com.android.gallery3d.filtershow.filters.FilterDrawRepresentation.StrokeData;
import com.android.gallery3d.filtershow.tools.TruePortraitNativeEngine;
public class ImageTruePortraitMask extends ImageShow {
private static final String LOGTAG = "ImageTruePortraitMask";
private boolean mZoomIn = false;
private Point mScaleTranslation = new Point();
private float mScaleStartFocusX, mScaleStartFocusY;
private Point mTranslation = new Point();
private Point mOriginalTranslation = new Point();
private float mScaleFactor = 1f;
private float mMaxScaleFactor = 3f;
private Paint mMaskPaintForeground = new Paint();
private Paint mMaskPaintBackground = new Paint();
private Paint mMaskEditPaint = new Paint();
private StrokeData mCurrent;
private Stack<StrokeData> mDrawing = new Stack<StrokeData>();
private EditorTruePortraitMask mEditor;
private Bitmap mPreview;
private Bitmap mOriginalMask;
private Bitmap mMask;
private Mode mMode = Mode.MODE_NONE;
private enum Mode {
MODE_NONE,
MODE_MOVE,
MODE_SCALE,
MODE_DRAW
}
public ImageTruePortraitMask(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setup();
}
public ImageTruePortraitMask(Context context, AttributeSet attrs) {
super(context, attrs);
setup();
}
public ImageTruePortraitMask(Context context) {
super(context);
setup();
}
private void setup() {
mMaskPaintForeground.setAntiAlias(true);
mMaskPaintForeground.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));
mMaskPaintForeground.setColorFilter(new ColorMatrixColorFilter(new float[] {
// Set color to GREEN
0,0,0,0,0,
0,0,0,0,191,
0,0,0,0,0,
0,0,0,.5f,0
}));
mMaskPaintBackground.setAntiAlias(true);
mMaskPaintBackground.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));
mMaskPaintBackground.setColorFilter(new ColorMatrixColorFilter(new float[] {
// Set color to RED and inverse ALPHA channel
// of original mask to get background
0,0,0,0,191,
0,0,0,0,0,
0,0,0,0,0,
0,0,0,-.5f,127
}));
mMaskEditPaint.setAntiAlias(true);
mMaskEditPaint.setStyle(Style.STROKE);
mMaskEditPaint.setStrokeCap(Paint.Cap.ROUND);
}
@Override
public void attach() {
mPreview = TruePortraitNativeEngine.getInstance().getPreview();
mOriginalMask = TruePortraitNativeEngine.getInstance().getMask();
mMask = Bitmap.createBitmap(mOriginalMask.getWidth(), mOriginalMask.getHeight(), mOriginalMask.getConfig());
}
@Override
public void detach() {
if(mPreview != null) {
mPreview.recycle();
mPreview = null;
}
if(mOriginalMask != null) {
mOriginalMask.recycle();
mOriginalMask = null;
}
if(mMask != null) {
mMask.recycle();
mMask = null;
}
mDrawing.clear();
}
public void setEditor(EditorTruePortraitMask editor) {
mEditor = editor;
}
@Override
public void onDraw(Canvas canvas) {
toggleComparisonButtonVisibility();
mPaint.reset();
mPaint.setAntiAlias(true);
mPaint.setFilterBitmap(true);
drawImage(canvas, mPreview, mPaint);
updateEditsToMask(mMask);
drawImage(canvas, mMask,
mEditor.isBackgroundMode()?mMaskPaintBackground:mMaskPaintForeground);
if (!mEdgeEffect.isFinished()) {
canvas.save();
float dx = (getHeight() - getWidth()) / 2f;
if (getWidth() > getHeight()) {
dx = - (getWidth() - getHeight()) / 2f;
}
if (mCurrentEdgeEffect == EDGE_BOTTOM) {
canvas.rotate(180, getWidth()/2, getHeight()/2);
} else if (mCurrentEdgeEffect == EDGE_RIGHT) {
canvas.rotate(90, getWidth()/2, getHeight()/2);
canvas.translate(0, dx);
} else if (mCurrentEdgeEffect == EDGE_LEFT) {
canvas.rotate(270, getWidth()/2, getHeight()/2);
canvas.translate(0, dx);
}
if (mCurrentEdgeEffect != 0) {
mEdgeEffect.draw(canvas);
}
canvas.restore();
invalidate();
} else {
mCurrentEdgeEffect = 0;
}
}
private void drawImage(Canvas canvas, Bitmap image, Paint paint) {
if (image == null) {
return;
}
Matrix m = computeImageToScreen(image.getWidth(), image.getHeight());
if (m == null) {
return;
}
canvas.save();
image.setHasAlpha(true);
canvas.drawBitmap(image, m, paint);
canvas.restore();
}
private void updateEditsToMask(Bitmap mask) {
// clear mask and reset with original
mask.eraseColor(Color.TRANSPARENT);
Canvas canvas = new Canvas(mask);
canvas.drawBitmap(mOriginalMask, 0, 0, null);
for(StrokeData sd:mDrawing) {
drawEdit(canvas, sd);
}
// update current edit
if(mCurrent != null) {
drawEdit(canvas, mCurrent);
}
}
private void drawEdit(Canvas canvas, StrokeData sd) {
if (sd == null) {
return;
}
if (sd.mPath == null) {
return;
}
mMaskEditPaint.setColor(sd.mColor);
if(sd.mColor == Color.TRANSPARENT)
mMaskEditPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
else
mMaskEditPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
mMaskEditPaint.setStrokeWidth(sd.mRadius);
canvas.drawPath(sd.mPath, mMaskEditPaint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
action = action & MotionEvent.ACTION_MASK;
boolean scaleInProgress = scaleInProgress();
mGestureDetector.onTouchEvent(event);
mScaleGestureDetector.onTouchEvent(event);
if (mMode == Mode.MODE_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) {
mTouchDown.x = ex;
mTouchDown.y = ey;
if(event.getPointerCount() == 2) {
mMode = Mode.MODE_MOVE;
setOriginalTranslation(getTranslation());
} else if (event.getPointerCount() == 1) {
mMode = Mode.MODE_DRAW;
Point mapPoint = mapPoint(ex, ey);
startNewSection(mapPoint.x, mapPoint.y);
}
} else if (action == MotionEvent.ACTION_MOVE) {
mTouch.x = ex;
mTouch.y = ey;
float scaleFactor = getScaleFactor();
if (scaleFactor > 1 && event.getPointerCount() == 2
&& mMode == Mode.MODE_MOVE) {
float translateX = (mTouch.x - mTouchDown.x) / scaleFactor;
float translateY = (mTouch.y - mTouchDown.y) / scaleFactor;
Point originalTranslation = getOriginalTranslation();
Point translation = getTranslation();
translation.x = (int) (originalTranslation.x + translateX);
translation.y = (int) (originalTranslation.y + translateY);
setTranslation(translation);
} else if(event.getPointerCount() == 1
&& mMode == Mode.MODE_DRAW) {
Point mapPoint = mapPoint(ex, ey);
addPoint(mapPoint.x, mapPoint.y);
}
} else if (action == MotionEvent.ACTION_UP
|| action == MotionEvent.ACTION_CANCEL
|| action == MotionEvent.ACTION_OUTSIDE) {
if(event.getPointerCount() == 1
&& mMode == Mode.MODE_DRAW) {
Point mapPoint = mapPoint(ex, ey);
endSection(mapPoint.x, mapPoint.y);
}
mMode = Mode.MODE_NONE;
mTouchDown.x = 0;
mTouchDown.y = 0;
mTouch.x = 0;
mTouch.y = 0;
if (getScaleFactor() <= 1) {
setScaleFactor(1);
setTranslation(new Point(0,0));
}
}
float scaleFactor = getScaleFactor();
Point translation = getTranslation();
constrainTranslation(translation, scaleFactor);
setTranslation(translation);
invalidate();
return true;
}
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 = getTranslation();
translation.x = (Integer) animation.getAnimatedValue();
setTranslation(translation);
invalidate();
}
});
mAnimatorTranslateY.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Point translation = getTranslation();
translation.y = (Integer) animation.getAnimatedValue();
setTranslation(translation);
invalidate();
}
});
mAnimatorTranslateX.start();
mAnimatorTranslateY.start();
}
private void applyTranslationConstraints() {
float scaleFactor = getScaleFactor();
Point translation = 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);
}
}
@Override
public boolean onDoubleTap(MotionEvent arg0) {
if(hasFusionApplied())
return true;
mZoomIn = !mZoomIn;
float scale = 1.0f;
final float x = arg0.getX();
final float y = arg0.getY();
if (mZoomIn) {
scale = getMaxScaleFactor();
}
if (scale != getScaleFactor()) {
if (mAnimatorScale != null) {
mAnimatorScale.cancel();
}
mAnimatorScale = ValueAnimator.ofFloat(
getScaleFactor(),
scale
);
float translateX = (getWidth() / 2 - x);
float translateY = (getHeight() / 2 - y);
Point translation = getTranslation();
int startTranslateX = translation.x;
int startTranslateY = translation.y;
if (scale != 1.0f) {
translation.x = (int) (mScaleTranslation.x + translateX);
translation.y = (int) (mScaleTranslation.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) {
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;
}
protected void constrainTranslation(Point translation, float scale) {
int currentEdgeEffect = 0;
if (mAllowScaleAndTranslate || scale <= 1) {
mCurrentEdgeEffect = 0;
mEdgeEffect.finish();
return;
}
Rect originalBounds = MasterImage.getImage().getOriginalBounds();
Matrix originalToScreen = computeImageToScreen(originalBounds.width(), originalBounds.height());
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);
}
}
@Override
public boolean onScale(ScaleGestureDetector detector) {
float scaleFactor = getScaleFactor();
scaleFactor = scaleFactor * detector.getScaleFactor();
if (scaleFactor > getMaxScaleFactor()) {
scaleFactor = getMaxScaleFactor();
}
if (scaleFactor < 1.0f) {
scaleFactor = 1.0f;
}
setScaleFactor(scaleFactor);
float focusx = detector.getFocusX();
float focusy = detector.getFocusY();
float translateX = (focusx - mScaleStartFocusX) / scaleFactor;
float translateY = (focusy - mScaleStartFocusY) / scaleFactor;
Point translation = getTranslation();
translation.x = (int) (mScaleTranslation.x + translateX);
translation.y = (int) (mScaleTranslation.y + translateY);
setTranslation(translation);
invalidate();
return true;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
Point pos = getTranslation();
mScaleTranslation.x = pos.x;
mScaleTranslation.y = pos.y;
mScaleStartFocusX = detector.getFocusX();
mScaleStartFocusY = detector.getFocusY();
mMode = Mode.MODE_SCALE;
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
mMode = Mode.MODE_NONE;
if (getScaleFactor() < 1) {
setScaleFactor(1);
invalidate();
}
}
@Override
public boolean hasFusionApplied() {
return false;
}
private Matrix computeImageToScreen(int bmWidth, int bmHeight) {
float scale = 1f;
float translateX = 0;
float translateY = 0;
RectF size = new RectF(0, 0,
bmWidth, bmHeight);
float scaleW = getWidth() / size.width();
float scaleH = getHeight() / size.height();
scale = Math.min(scaleW, scaleH);
translateX = (getWidth() - (size.width() * scale)) / 2.0f;
translateY = (getHeight() - (size.height() * scale)) / 2.0f;
Point translation = getTranslation();
Matrix m = new Matrix();
m.postScale(scale, scale);
m.postTranslate(translateX, translateY);
m.postScale(getScaleFactor(), getScaleFactor(),
getWidth() / 2.0f,
getHeight() / 2.0f);
m.postTranslate(translation.x * getScaleFactor(),
translation.y * getScaleFactor());
return m;
}
private Matrix computeScreenToImage(int bmWidth, int bmHeight) {
Matrix m = computeImageToScreen(bmWidth, bmHeight);
Matrix inverse = new Matrix();
m.invert(inverse);
return inverse;
}
private Point mapPoint(int x, int y) {
Matrix screenToImage = computeScreenToImage(mMask.getWidth(), mMask.getHeight());
float[] tmpPoint = {x,y};
screenToImage.mapPoints(tmpPoint);
return new Point((int)tmpPoint[0], (int)tmpPoint[1]);
}
private void setTranslation(Point translation) {
mTranslation.x = translation.x;
mTranslation.y = translation.y;
}
private void setOriginalTranslation(Point translation) {
mOriginalTranslation.x = translation.x;
mOriginalTranslation.y = translation.y;
}
private void setScaleFactor(float scale) {
mScaleFactor = scale;
}
private Point getTranslation() {
return mTranslation;
}
private Point getOriginalTranslation() {
return mOriginalTranslation;
}
private float getScaleFactor() {
return mScaleFactor;
}
private float getMaxScaleFactor() {
return mMaxScaleFactor;
}
private void fillStrokeParameters(StrokeData sd){
sd.mColor = mEditor.isBackgroundMode()?Color.TRANSPARENT:Color.BLACK;
sd.mRadius = mEditor.getBrushSize();
}
private void startNewSection(float x, float y) {
mCurrent = new StrokeData();
fillStrokeParameters(mCurrent);
mCurrent.mPath = new Path();
mCurrent.mPath.moveTo(x, y);
mCurrent.mPoints[0] = x;
mCurrent.mPoints[1] = y;
mCurrent.noPoints = 1;
}
private void addPoint(float x, float y) {
int len = mCurrent.noPoints * 2;
mCurrent.mPath.lineTo(x, y);
if ((len+2) > mCurrent.mPoints.length) {
mCurrent.mPoints = Arrays.copyOf(mCurrent.mPoints, mCurrent.mPoints.length * 2);
}
mCurrent.mPoints[len] = x;
mCurrent.mPoints[len + 1] = y;
mCurrent.noPoints++;
}
private void endSection(float x, float y) {
addPoint(x, y);
mDrawing.push(mCurrent);
mCurrent = null;
mEditor.refreshUndoButton();
}
private void resetDrawing() {
mCurrent = null;
}
public boolean canUndoEdit() {
return !mDrawing.isEmpty();
}
public void undoLastEdit() {
if(!mDrawing.isEmpty()) {
mDrawing.pop();
mEditor.refreshUndoButton();
invalidate();
}
}
public void applyMaskUpdates() {
TruePortraitNativeEngine.getInstance().updateMask(mDrawing);
}
}