2 * Copyright (C) 2012 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.gallery3d.filtershow.imageshow;
19 import android.content.Context;
20 import android.graphics.Bitmap;
21 import android.graphics.Canvas;
22 import android.graphics.Color;
23 import android.graphics.Matrix;
24 import android.graphics.Paint;
25 import android.graphics.Paint.Style;
26 import android.graphics.Path;
27 import android.graphics.RectF;
28 import android.util.AttributeSet;
29 import android.view.MotionEvent;
30 import android.view.View;
32 import com.android.gallery3d.filtershow.filters.FilterMirrorRepresentation.Mirror;
33 import com.android.gallery3d.filtershow.filters.FilterRepresentation;
34 import com.android.gallery3d.filtershow.history.HistoryItem;
35 import com.android.gallery3d.filtershow.pipeline.ImagePreset;
37 public abstract class ImageGeometry extends ImageShow {
38 protected boolean mVisibilityGained = false;
39 private boolean mHasDrawn = false;
41 protected static final float MAX_STRAIGHTEN_ANGLE = 45;
42 protected static final float MIN_STRAIGHTEN_ANGLE = -45;
44 protected float mCenterX;
45 protected float mCenterY;
47 protected float mCurrentX;
48 protected float mCurrentY;
49 protected float mTouchCenterX;
50 protected float mTouchCenterY;
52 // Local geometry data
53 private GeometryMetadata mLocalGeometry = null;
54 private RectF mLocalDisplayBounds = null;
55 protected float mXOffset = 0;
56 protected float mYOffset = 0;
58 protected enum MODES {
62 protected MODES mMode = MODES.NONE;
64 private static final String LOGTAG = "ImageGeometry";
66 public ImageGeometry(Context context, AttributeSet attrs) {
67 super(context, attrs);
70 public ImageGeometry(Context context) {
74 private void setupLocalDisplayBounds(RectF b) {
75 mLocalDisplayBounds = b;
76 calculateLocalScalingFactorAndOffset();
79 protected static float angleFor(float dx, float dy) {
80 return (float) (Math.atan2(dx, dy) * 180 / Math.PI);
83 protected static int snappedAngle(float angle) {
84 float remainder = angle % 90;
85 int current = (int) (angle / 90); // truncates
86 if (remainder < -45) {
88 } else if (remainder > 45) {
94 protected float getCurrentTouchAngle() {
95 if (mCurrentX == mTouchCenterX && mCurrentY == mTouchCenterY) {
98 float dX1 = mTouchCenterX - mCenterX;
99 float dY1 = mTouchCenterY - mCenterY;
100 float dX2 = mCurrentX - mCenterX;
101 float dY2 = mCurrentY - mCenterY;
103 float angleA = angleFor(dX1, dY1);
104 float angleB = angleFor(dX2, dY2);
105 return (angleB - angleA) % 360;
108 protected float computeScale(float width, float height) {
109 float imageWidth = mLocalGeometry.getPhotoBounds().width();
110 float imageHeight = mLocalGeometry.getPhotoBounds().height();
111 return GeometryMath.scale(imageWidth, imageHeight, width, height);
114 private void calculateLocalScalingFactorAndOffset() {
115 if (mLocalGeometry == null || mLocalDisplayBounds == null)
117 RectF imageBounds = mLocalGeometry.getPhotoBounds();
118 float imageWidth = imageBounds.width();
119 float imageHeight = imageBounds.height();
120 float displayWidth = mLocalDisplayBounds.width();
121 float displayHeight = mLocalDisplayBounds.height();
123 mCenterX = displayWidth / 2;
124 mCenterY = displayHeight / 2;
125 mYOffset = (displayHeight - imageHeight) / 2.0f;
126 mXOffset = (displayWidth - imageWidth) / 2.0f;
131 public void resetParameter() {
132 super.resetParameter();
134 setLocalStraighten(0);
135 setLocalCropBounds(getLocalPhotoBounds());
136 setLocalMirror(Mirror.NONE);
141 // Overwrites local with master
142 public void syncLocalToMasterGeometry() {
143 mLocalGeometry = getGeometry();
144 calculateLocalScalingFactorAndOffset();
147 protected RectF getLocalPhotoBounds() {
148 return mLocalGeometry.getPhotoBounds();
151 protected RectF getLocalCropBounds() {
152 return mLocalGeometry.getPreviewCropBounds();
155 protected RectF getLocalDisplayBounds() {
156 return new RectF(mLocalDisplayBounds);
159 protected float getLocalScale() {
160 return mLocalGeometry.getScaleFactor();
163 protected float getLocalRotation() {
164 return mLocalGeometry.getRotation();
167 protected float getLocalStraighten() {
168 return mLocalGeometry.getStraightenRotation();
171 protected void setLocalScale(float s) {
172 mLocalGeometry.setScaleFactor(s);
175 protected void updateScale() {
176 RectF bounds = getUntranslatedStraightenCropBounds(mLocalGeometry.getPhotoBounds(),
177 getLocalStraighten());
178 float zoom = computeScale(bounds.width(), bounds.height());
182 protected void setLocalRotation(int r) {
183 mLocalGeometry.setRotation(r);
188 * Constrains rotation to be in [0, 90, 180, 270].
190 protected int constrainedRotation(float rotation) {
191 int r = (int) ((rotation % 360) / 90);
192 r = (r < 0) ? (r + 4) : r;
196 protected boolean isHeightWidthSwapped() {
197 return ((int) (getLocalRotation() / 90)) % 2 != 0;
200 protected void setLocalStraighten(float r) {
201 mLocalGeometry.setStraightenRotation(r);
205 protected void setLocalCropBounds(RectF c) {
206 mLocalGeometry.setCropBounds(c);
210 protected Mirror getLocalMirror() {
211 return mLocalGeometry.getMirrorType();
214 protected void setLocalMirror(Mirror flip) {
215 mLocalGeometry.setMirrorType(flip);
218 protected float getTotalLocalRotation() {
219 return getLocalRotation() + getLocalStraighten();
222 protected static Path drawClosedPath(Canvas canvas, Paint paint, float[] points) {
223 Path crop = new Path();
224 crop.moveTo(points[0], points[1]);
225 crop.lineTo(points[2], points[3]);
226 crop.lineTo(points[4], points[5]);
227 crop.lineTo(points[6], points[7]);
229 canvas.drawPath(crop, paint);
233 protected static float getNewHeightForWidthAspect(float width, float w, float h) {
234 return width * h / w;
237 protected static float getNewWidthForHeightAspect(float height, float w, float h) {
238 return height * w / h;
242 protected void onVisibilityChanged(View changedView, int visibility) {
243 super.onVisibilityChanged(changedView, visibility);
244 if (visibility == View.VISIBLE) {
245 mVisibilityGained = true;
246 MasterImage.getImage().invalidateFiltersOnly();
247 syncLocalToMasterGeometry();
251 if (mVisibilityGained == true && mHasDrawn == true) {
254 mVisibilityGained = false;
259 protected void gainedVisibility() {
260 // Override this stub.
263 protected void lostVisibility() {
264 // Override this stub.
268 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
269 super.onSizeChanged(w, h, oldw, oldh);
270 setupLocalDisplayBounds(new RectF(0, 0, w, h));
274 public boolean onTouchEvent(MotionEvent event) {
275 switch (event.getActionMasked()) {
276 case (MotionEvent.ACTION_DOWN):
277 setActionDown(event.getX(), event.getY());
279 case (MotionEvent.ACTION_UP):
283 case (MotionEvent.ACTION_MOVE):
284 setActionMove(event.getX(), event.getY());
293 protected int getLocalValue() {
294 return 0; // Override this
297 protected void setActionDown(float x, float y) {
305 protected void setActionMove(float x, float y) {
311 protected void setActionUp() {
315 protected void setNoAction() {
319 public void saveAndSetPreset() {
320 FilterRepresentation lastAction = null;
321 HistoryItem historyItem = MasterImage.getImage().getHistory().getLast();
322 if (historyItem != null) {
323 lastAction = historyItem.getFilterRepresentation();
326 if (lastAction != null
327 && lastAction.getSerializationName().equalsIgnoreCase(
328 GeometryMetadata.SERIALIZATION_NAME)) {
329 getImagePreset().setGeometry(mLocalGeometry);
330 resetImageCaches(this);
332 if (mLocalGeometry.hasModifications()) {
333 ImagePreset copy = new ImagePreset(getImagePreset());
334 copy.setGeometry(mLocalGeometry);
335 // TODO: we should have multiple filter representations for each
336 // of the different geometry operations we have, otherwise we cannot
337 // differentiate them in the history/state
338 MasterImage.getImage().setPreset(copy, copy.getGeometry(), true);
341 MasterImage.getImage().notifyGeometryChange();
345 public static RectF getUntranslatedStraightenCropBounds(RectF imageRect, float straightenAngle) {
346 float deg = straightenAngle;
350 double a = Math.toRadians(deg);
351 double sina = Math.sin(a);
352 double cosa = Math.cos(a);
354 double rw = imageRect.width();
355 double rh = imageRect.height();
356 double h1 = rh * rh / (rw * sina + rh * cosa);
357 double h2 = rh * rw / (rw * cosa + rh * sina);
358 double hh = Math.min(h1, h2);
359 double ww = hh * rw / rh;
361 float left = (float) ((rw - ww) * 0.5f);
362 float top = (float) ((rh - hh) * 0.5f);
363 float right = (float) (left + ww);
364 float bottom = (float) (top + hh);
366 return new RectF(left, top, right, bottom);
369 protected RectF straightenBounds() {
370 RectF bounds = getUntranslatedStraightenCropBounds(getLocalPhotoBounds(),
371 getLocalStraighten());
372 float scale = computeScale(getWidth(), getHeight());
373 bounds = GeometryMath.scaleRect(bounds, scale);
374 float dx = (getWidth() / 2) - bounds.centerX();
375 float dy = (getHeight() / 2) - bounds.centerY();
376 bounds.offset(dx, dy);
380 protected static void drawRotatedShadows(Canvas canvas, Paint p, RectF innerBounds,
382 float rotation, float centerX, float centerY) {
384 canvas.rotate(rotation, centerX, centerY);
386 float x = (outerBounds.left - outerBounds.right);
387 float y = (outerBounds.top - outerBounds.bottom);
388 float longest = (float) Math.sqrt(x * x + y * y) / 2;
389 float minX = centerX - longest;
390 float maxX = centerX + longest;
391 float minY = centerY - longest;
392 float maxY = centerY + longest;
393 canvas.drawRect(minX, minY, innerBounds.right, innerBounds.top, p);
394 canvas.drawRect(minX, innerBounds.top, innerBounds.left, maxY, p);
395 canvas.drawRect(innerBounds.left, innerBounds.bottom, maxX, maxY,
397 canvas.drawRect(innerBounds.right, minY, maxX,
398 innerBounds.bottom, p);
399 canvas.rotate(-rotation, centerX, centerY);
403 protected void drawShadows(Canvas canvas, Paint p, RectF innerBounds) {
404 float w = getWidth();
405 float h = getHeight();
406 canvas.drawRect(0f, 0f, w, innerBounds.top, p);
407 canvas.drawRect(0f, innerBounds.top, innerBounds.left, innerBounds.bottom, p);
408 canvas.drawRect(innerBounds.right, innerBounds.top, w, innerBounds.bottom, p);
409 canvas.drawRect(0f, innerBounds.bottom, w, h, p);
413 public void onDraw(Canvas canvas) {
414 Bitmap image = getFiltersOnlyImage();
421 drawShape(canvas, image);
424 protected void drawShape(Canvas canvas, Bitmap image) {
425 // TODO: Override this stub.
429 * Sets up inputs for buildCenteredPhotoMatrix and buildWanderingCropMatrix
430 * and returns the scale factor.
432 protected float getTransformState(RectF photo, RectF crop, float[] displayCenter) {
433 RectF photoBounds = getLocalPhotoBounds();
434 RectF cropBounds = getLocalCropBounds();
435 float scale = computeScale(getWidth(), getHeight());
436 // checks if local rotation is an odd multiple of 90.
437 if (isHeightWidthSwapped()) {
438 scale = computeScale(getHeight(), getWidth());
440 // put in screen coordinates
442 crop.set(GeometryMath.scaleRect(cropBounds, scale));
445 photo.set(GeometryMath.scaleRect(photoBounds, scale));
447 if (displayCenter != null && displayCenter.length >= 2) {
448 displayCenter[0] = getWidth() / 2f;
449 displayCenter[1] = getHeight() / 2f;
454 protected RectF drawTransformed(Canvas canvas, Bitmap photo, Paint p, float[] offset) {
455 p.setARGB(255, 0, 0, 0);
456 float[] displayCenter = new float[2];
457 RectF scaledCrop = new RectF();
458 RectF scaledPhoto = new RectF();
459 float scale = getTransformState(scaledPhoto, scaledCrop, displayCenter);
460 Matrix m = GeometryMetadata.buildCenteredPhotoMatrix(scaledPhoto, scaledCrop,
461 getLocalRotation(), getLocalStraighten(), getLocalMirror(), displayCenter);
463 Matrix m1 = GeometryMetadata.buildWanderingCropMatrix(scaledPhoto, scaledCrop,
464 getLocalRotation(), getLocalStraighten(), getLocalMirror(), displayCenter);
465 m1.mapRect(scaledCrop);
466 Path path = new Path();
467 scaledCrop.offset(-offset[0], -offset[1]);
468 path.addRect(scaledCrop, Path.Direction.CCW);
470 m.preScale(scale, scale);
471 m.postTranslate(-offset[0], -offset[1]);
473 canvas.drawBitmap(photo, m, p);
476 p.setColor(Color.WHITE);
477 p.setStyle(Style.STROKE);
479 canvas.drawPath(path, p);
481 p.setColor(mBackgroundColor);
483 p.setStyle(Paint.Style.FILL);
484 drawShadows(canvas, p, scaledCrop);
488 protected void drawTransformedCropped(Canvas canvas, Bitmap photo, Paint p) {
489 RectF photoBounds = getLocalPhotoBounds();
490 RectF cropBounds = getLocalCropBounds();
491 float imageWidth = cropBounds.width();
492 float imageHeight = cropBounds.height();
493 float scale = GeometryMath.scale(imageWidth, imageHeight, getWidth(), getHeight());
494 // checks if local rotation is an odd multiple of 90.
495 if (isHeightWidthSwapped()) {
496 scale = GeometryMath.scale(imageWidth, imageHeight, getHeight(), getWidth());
498 // put in screen coordinates
499 RectF scaledCrop = GeometryMath.scaleRect(cropBounds, scale);
500 RectF scaledPhoto = GeometryMath.scaleRect(photoBounds, scale);
501 float[] displayCenter = {
502 getWidth() / 2f, getHeight() / 2f
504 Matrix m1 = GeometryMetadata.buildWanderingCropMatrix(scaledPhoto, scaledCrop,
505 getLocalRotation(), getLocalStraighten(), getLocalMirror(), displayCenter);
506 float[] cropCenter = {
507 scaledCrop.centerX(), scaledCrop.centerY()
509 m1.mapPoints(cropCenter);
510 GeometryMetadata.concatRecenterMatrix(m1, cropCenter, displayCenter);
511 m1.preRotate(getLocalStraighten(), scaledPhoto.centerX(), scaledPhoto.centerY());
512 m1.preScale(scale, scale);
514 p.setARGB(255, 0, 0, 0);
516 canvas.drawBitmap(photo, m1, p);
519 p.setColor(mBackgroundColor);
520 p.setStyle(Paint.Style.FILL);
521 scaledCrop.offset(displayCenter[0] - scaledCrop.centerX(), displayCenter[1]
522 - scaledCrop.centerY());
523 RectF display = new RectF(0, 0, getWidth(), getHeight());
524 drawRotatedShadows(canvas, p, scaledCrop, display, getLocalRotation(), getWidth() / 2,