blob: 42dd139bc29b95e3fbfd180aeb93a5393f41f0ed [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.Paint.Style;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import com.android.gallery3d.filtershow.imageshow.GeometryMetadata.FLIP;
import com.android.gallery3d.filtershow.presets.ImagePreset;
public abstract class ImageGeometry extends ImageSlave {
private boolean mVisibilityGained = false;
private boolean mHasDrawn = false;
protected static final float MAX_STRAIGHTEN_ANGLE = 45;
protected static final float MIN_STRAIGHTEN_ANGLE = -45;
protected float mCenterX;
protected float mCenterY;
protected float mCurrentX;
protected float mCurrentY;
protected float mTouchCenterX;
protected float mTouchCenterY;
// Local geometry data
private GeometryMetadata mLocalGeometry = null;
private RectF mLocalDisplayBounds = null;
protected float mXOffset = 0;
protected float mYOffset = 0;
protected enum MODES {
NONE, DOWN, UP, MOVE
}
protected MODES mMode = MODES.NONE;
private static final String LOGTAG = "ImageGeometry";
public ImageGeometry(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ImageGeometry(Context context) {
super(context);
}
private void setupLocalDisplayBounds(RectF b) {
mLocalDisplayBounds = b;
calculateLocalScalingFactorAndOffset();
}
protected static float angleFor(float dx, float dy) {
return (float) (Math.atan2(dx, dy) * 180 / Math.PI);
}
protected static int snappedAngle(float angle) {
float remainder = angle % 90;
int current = (int) (angle / 90); // truncates
if (remainder < -45) {
--current;
} else if (remainder > 45) {
++current;
}
return current * 90;
}
protected float getCurrentTouchAngle() {
if (mCurrentX == mTouchCenterX && mCurrentY == mTouchCenterY) {
return 0;
}
float dX1 = mTouchCenterX - mCenterX;
float dY1 = mTouchCenterY - mCenterY;
float dX2 = mCurrentX - mCenterX;
float dY2 = mCurrentY - mCenterY;
float angleA = angleFor(dX1, dY1);
float angleB = angleFor(dX2, dY2);
return (angleB - angleA) % 360;
}
protected float computeScale(float width, float height) {
float imageWidth = mLocalGeometry.getPhotoBounds().width();
float imageHeight = mLocalGeometry.getPhotoBounds().height();
return GeometryMath.scale(imageWidth, imageHeight, width, height);
}
private void calculateLocalScalingFactorAndOffset() {
if (mLocalGeometry == null || mLocalDisplayBounds == null)
return;
RectF imageBounds = mLocalGeometry.getPhotoBounds();
float imageWidth = imageBounds.width();
float imageHeight = imageBounds.height();
float displayWidth = mLocalDisplayBounds.width();
float displayHeight = mLocalDisplayBounds.height();
mCenterX = displayWidth / 2;
mCenterY = displayHeight / 2;
mYOffset = (displayHeight - imageHeight) / 2.0f;
mXOffset = (displayWidth - imageWidth) / 2.0f;
updateScale();
}
@Override
public void resetParameter() {
super.resetParameter();
setLocalRotation(0);
setLocalStraighten(0);
setLocalCropBounds(getLocalPhotoBounds());
setLocalFlip(FLIP.NONE);
saveAndSetPreset();
invalidate();
}
// Overwrites local with master
protected void syncLocalToMasterGeometry() {
mLocalGeometry = getMaster().getGeometry();
calculateLocalScalingFactorAndOffset();
}
protected RectF getLocalPhotoBounds() {
return mLocalGeometry.getPhotoBounds();
}
protected RectF getLocalCropBounds() {
return mLocalGeometry.getPreviewCropBounds();
}
protected RectF getLocalDisplayBounds() {
return new RectF(mLocalDisplayBounds);
}
protected float getLocalScale() {
return mLocalGeometry.getScaleFactor();
}
protected float getLocalRotation() {
return mLocalGeometry.getRotation();
}
protected float getLocalStraighten() {
return mLocalGeometry.getStraightenRotation();
}
protected void setLocalScale(float s) {
mLocalGeometry.setScaleFactor(s);
}
protected void updateScale() {
RectF bounds = getUntranslatedStraightenCropBounds(mLocalGeometry.getPhotoBounds(),
getLocalStraighten());
float zoom = computeScale(bounds.width(), bounds.height());
setLocalScale(zoom);
}
protected void setLocalRotation(float r) {
mLocalGeometry.setRotation(r);
updateScale();
}
/**
* Constrains rotation to be in [0, 90, 180, 270].
*/
protected int constrainedRotation(float rotation) {
int r = (int) ((rotation % 360) / 90);
r = (r < 0) ? (r + 4) : r;
return r * 90;
}
protected Matrix getLocalGeoFlipMatrix(float width, float height) {
return mLocalGeometry.getFlipMatrix(width, height);
}
protected void setLocalStraighten(float r) {
mLocalGeometry.setStraightenRotation(r);
updateScale();
}
protected void setLocalCropBounds(RectF c) {
mLocalGeometry.setCropBounds(c);
updateScale();
}
protected FLIP getLocalFlip() {
return mLocalGeometry.getFlipType();
}
protected void setLocalFlip(FLIP flip) {
mLocalGeometry.setFlipType(flip);
}
protected float getTotalLocalRotation() {
return getLocalRotation() + getLocalStraighten();
}
protected static float[] getCornersFromRect(RectF r) {
// Order is:
// 0------->1
// ^ |
// | v
// 3<-------2
float[] corners = {
r.left, r.top, // 0
r.right, r.top, // 1
r.right, r.bottom,// 2
r.left, r.bottom // 3
};
return corners;
}
// If edge point [x, y] in array [x0, y0, x1, y1, ...] is outside of the
// image bound rectangle, clamps it to the edge of the rectangle.
protected static void getEdgePoints(RectF imageBound, float[] array) {
if (array.length < 2)
return;
for (int x = 0; x < array.length; x += 2) {
array[x] = GeometryMath.clamp(array[x], imageBound.left, imageBound.right);
array[x + 1] = GeometryMath.clamp(array[x + 1], imageBound.top, imageBound.bottom);
}
}
protected static Path drawClosedPath(Canvas canvas, Paint paint, float[] points) {
Path crop = new Path();
crop.moveTo(points[0], points[1]);
crop.lineTo(points[2], points[3]);
crop.lineTo(points[4], points[5]);
crop.lineTo(points[6], points[7]);
crop.close();
canvas.drawPath(crop, paint);
return crop;
}
protected static void fixAspectRatio(RectF r, float w, float h) {
float scale = Math.min(r.width() / w, r.height() / h);
float centX = r.centerX();
float centY = r.centerY();
float hw = scale * w / 2;
float hh = scale * h / 2;
r.set(centX - hw, centY - hh, centX + hw, centY + hh);
}
protected static float getNewHeightForWidthAspect(float width, float w, float h) {
return width * h / w;
}
protected static float getNewWidthForHeightAspect(float height, float w, float h) {
return height * w / h;
}
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
if (visibility == View.VISIBLE) {
mVisibilityGained = true;
syncLocalToMasterGeometry();
updateScale();
gainedVisibility();
} else {
if (mVisibilityGained == true && mHasDrawn == true) {
lostVisibility();
}
mVisibilityGained = false;
mHasDrawn = false;
}
}
protected void gainedVisibility() {
// TODO: Override this stub.
}
protected void lostVisibility() {
// TODO: Override this stub.
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
setupLocalDisplayBounds(new RectF(0, 0, w, h));
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getActionMasked()) {
case (MotionEvent.ACTION_DOWN):
setActionDown(event.getX(), event.getY());
break;
case (MotionEvent.ACTION_UP):
setActionUp();
saveAndSetPreset();
break;
case (MotionEvent.ACTION_MOVE):
setActionMove(event.getX(), event.getY());
break;
default:
setNoAction();
}
if (getPanelController() != null) {
getPanelController().onNewValue(getLocalValue());
}
invalidate();
return true;
}
protected int getLocalValue() {
return 0; // TODO: Override this
}
protected void setActionDown(float x, float y) {
mTouchCenterX = x;
mTouchCenterY = y;
mCurrentX = x;
mCurrentY = y;
mMode = MODES.DOWN;
}
protected void setActionMove(float x, float y) {
mCurrentX = x;
mCurrentY = y;
mMode = MODES.MOVE;
}
protected void setActionUp() {
mMode = MODES.UP;
}
protected void setNoAction() {
mMode = MODES.NONE;
}
@Override
public boolean showTitle() {
return false;
}
public String getName() {
return "Geometry";
}
public void saveAndSetPreset() {
ImagePreset lastHistoryItem = getHistory().getLast();
if (lastHistoryItem != null && lastHistoryItem.historyName().equalsIgnoreCase(getName())) {
getImagePreset().setGeometry(mLocalGeometry);
resetImageCaches(this);
} else {
if (mLocalGeometry.hasModifications()) {
ImagePreset copy = new ImagePreset(getImagePreset());
copy.setGeometry(mLocalGeometry);
copy.setHistoryName(getName());
copy.setIsFx(false);
setImagePreset(copy, true);
}
}
invalidate();
}
public static RectF getUntranslatedStraightenCropBounds(RectF imageRect, float straightenAngle) {
float deg = straightenAngle;
if (deg < 0) {
deg = -deg;
}
double a = Math.toRadians(deg);
double sina = Math.sin(a);
double cosa = Math.cos(a);
double rw = imageRect.width();
double rh = imageRect.height();
double h1 = rh * rh / (rw * sina + rh * cosa);
double h2 = rh * rw / (rw * cosa + rh * sina);
double hh = Math.min(h1, h2);
double ww = hh * rw / rh;
float left = (float) ((rw - ww) * 0.5f);
float top = (float) ((rh - hh) * 0.5f);
float right = (float) (left + ww);
float bottom = (float) (top + hh);
return new RectF(left, top, right, bottom);
}
protected Matrix getGeoMatrix(RectF r, boolean onlyRotate) {
RectF pbounds = getLocalPhotoBounds();
float scale = GeometryMath
.scale(pbounds.width(), pbounds.height(), getWidth(), getHeight());
if (((int) (getLocalRotation() / 90)) % 2 != 0) {
scale = GeometryMath.scale(pbounds.width(), pbounds.height(), getHeight(), getWidth());
}
float yoff = getHeight() / 2;
float xoff = getWidth() / 2;
float w = r.left * 2 + r.width();
float h = r.top * 2 + r.height();
return mLocalGeometry.buildGeometryMatrix(w, h, scale, xoff, yoff, onlyRotate);
}
protected void drawImageBitmap(Canvas canvas, Bitmap bitmap, Paint paint, Matrix m) {
canvas.save();
canvas.drawBitmap(bitmap, m, paint);
canvas.restore();
}
protected void drawImageBitmap(Canvas canvas, Bitmap bitmap, Paint paint) {
float scale = computeScale(getWidth(), getHeight());
float yoff = getHeight() / 2;
float xoff = getWidth() / 2;
Matrix m = mLocalGeometry.buildGeometryUIMatrix(scale, xoff, yoff);
drawImageBitmap(canvas, bitmap, paint, m);
}
protected RectF straightenBounds() {
RectF bounds = getUntranslatedStraightenCropBounds(getLocalPhotoBounds(),
getLocalStraighten());
Matrix m = getGeoMatrix(bounds, true);
m.mapRect(bounds);
return bounds;
}
protected void drawStraighten(Canvas canvas, Paint paint) {
RectF bounds = straightenBounds();
canvas.save();
canvas.drawRect(bounds, paint);
canvas.restore();
}
protected RectF unrotatedCropBounds() {
RectF bounds = getLocalCropBounds();
RectF pbounds = getLocalPhotoBounds();
float scale = computeScale(getWidth(), getHeight());
float yoff = getHeight() / 2;
float xoff = getWidth() / 2;
Matrix m = mLocalGeometry.buildGeometryMatrix(pbounds.width(), pbounds.height(), scale,
xoff, yoff, 0);
m.mapRect(bounds);
return bounds;
}
protected RectF cropBounds() {
RectF bounds = getLocalCropBounds();
Matrix m = getGeoMatrix(getLocalPhotoBounds(), true);
m.mapRect(bounds);
return bounds;
}
// Fails for non-90 degree
protected void drawCrop(Canvas canvas, Paint paint) {
RectF bounds = cropBounds();
canvas.save();
canvas.drawRect(bounds, paint);
canvas.restore();
}
protected void drawCropSafe(Canvas canvas, Paint paint) {
Matrix m = getGeoMatrix(getLocalPhotoBounds(), true);
RectF crop = getLocalCropBounds();
if (!m.rectStaysRect()) {
float[] corners = getCornersFromRect(crop);
m.mapPoints(corners);
drawClosedPath(canvas, paint, corners);
} else {
m.mapRect(crop);
Path path = new Path();
path.addRect(crop, Path.Direction.CCW);
canvas.drawPath(path, paint);
}
}
protected void drawTransformedBitmap(Canvas canvas, Bitmap bitmap, Paint paint, boolean clip) {
paint.setARGB(255, 0, 0, 0);
drawImageBitmap(canvas, bitmap, paint);
paint.setColor(Color.WHITE);
paint.setStyle(Style.STROKE);
paint.setStrokeWidth(2);
drawCropSafe(canvas, paint);
paint.setColor(getDefaultBackgroundColor());
paint.setStyle(Paint.Style.FILL);
drawShadows(canvas, paint, unrotatedCropBounds());
}
protected void drawShadows(Canvas canvas, Paint p, RectF innerBounds) {
RectF display = new RectF(0, 0, getWidth(), getHeight());
drawShadows(canvas, p, innerBounds, display, getLocalRotation(), getWidth() / 2,
getHeight() / 2);
}
protected static void drawShadows(Canvas canvas, Paint p, RectF innerBounds, RectF outerBounds,
float rotation, float centerX, float centerY) {
canvas.save();
canvas.rotate(rotation, centerX, centerY);
float x = (outerBounds.left - outerBounds.right);
float y = (outerBounds.top - outerBounds.bottom);
float longest = (float) Math.sqrt(x * x + y * y) / 2;
float minX = centerX - longest;
float maxX = centerX + longest;
float minY = centerY - longest;
float maxY = centerY + longest;
canvas.drawRect(minX, minY, innerBounds.right, innerBounds.top, p);
canvas.drawRect(minX, innerBounds.top, innerBounds.left, maxY, p);
canvas.drawRect(innerBounds.left, innerBounds.bottom, maxX, maxY,
p);
canvas.drawRect(innerBounds.right, minY, maxX,
innerBounds.bottom, p);
canvas.rotate(-rotation, centerX, centerY);
canvas.restore();
}
@Override
public void onDraw(Canvas canvas) {
if (getDirtyGeometryFlag()) {
syncLocalToMasterGeometry();
clearDirtyGeometryFlag();
}
requestFilteredImages();
Bitmap image = getMaster().getFiltersOnlyImage();
if (image == null) {
invalidate();
return;
}
mHasDrawn = true;
drawShape(canvas, image);
}
protected void drawShape(Canvas canvas, Bitmap image) {
// TODO: Override this stub.
}
protected RectF drawTransformed(Canvas canvas, Bitmap photo, Paint p) {
p.setARGB(255, 0, 0, 0);
RectF photoBounds = getLocalPhotoBounds();
RectF cropBounds = getLocalCropBounds();
float scale = computeScale(getWidth(), getHeight());
// checks if local rotation is an odd multiple of 90.
if (((int) (getLocalRotation() / 90)) % 2 != 0) {
scale = computeScale(getHeight(), getWidth());
}
// put in screen coordinates
RectF scaledCrop = GeometryMath.scaleRect(cropBounds, scale);
RectF scaledPhoto = GeometryMath.scaleRect(photoBounds, scale);
float[] displayCenter = {
getWidth() / 2f, getHeight() / 2f
};
Matrix m = GeometryMetadata.buildCenteredPhotoMatrix(scaledPhoto, scaledCrop,
getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter);
Matrix m1 = GeometryMetadata.buildWanderingCropMatrix(scaledPhoto, scaledCrop,
getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter);
m1.mapRect(scaledCrop);
Path path = new Path();
path.addRect(scaledCrop, Path.Direction.CCW);
m.preScale(scale, scale);
canvas.save();
canvas.drawBitmap(photo, m, p);
canvas.restore();
p.setColor(Color.WHITE);
p.setStyle(Style.STROKE);
p.setStrokeWidth(2);
canvas.drawPath(path, p);
return scaledCrop;
}
protected void drawTransformedCropped(Canvas canvas, Bitmap photo, Paint p) {
RectF photoBounds = getLocalPhotoBounds();
RectF cropBounds = getLocalCropBounds();
float imageWidth = cropBounds.width();
float imageHeight = cropBounds.height();
float scale = GeometryMath.scale(imageWidth, imageHeight, getWidth(), getHeight());
// checks if local rotation is an odd multiple of 90.
if (((int) (getLocalRotation() / 90)) % 2 != 0) {
scale = GeometryMath.scale(imageWidth, imageHeight, getHeight(), getWidth());
}
// put in screen coordinates
RectF scaledCrop = GeometryMath.scaleRect(cropBounds, scale);
RectF scaledPhoto = GeometryMath.scaleRect(photoBounds, scale);
float[] displayCenter = {
getWidth() / 2f, getHeight() / 2f
};
Matrix m1 = GeometryMetadata.buildWanderingCropMatrix(scaledPhoto, scaledCrop,
getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter);
float[] cropCenter = {
scaledCrop.centerX(), scaledCrop.centerY()
};
m1.mapPoints(cropCenter);
GeometryMetadata.concatRecenterMatrix(m1, cropCenter, displayCenter);
m1.preRotate(getLocalStraighten(), scaledPhoto.centerX(), scaledPhoto.centerY());
m1.preScale(scale, scale);
p.setARGB(255, 0, 0, 0);
canvas.save();
canvas.drawBitmap(photo, m1, p);
canvas.restore();
p.setColor(getDefaultBackgroundColor());
p.setStyle(Paint.Style.FILL);
scaledCrop.offset(displayCenter[0] - scaledCrop.centerX(), displayCenter[1]
- scaledCrop.centerY());
drawShadows(canvas, p, scaledCrop);
}
}