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.imageshow.GeometryMetadata.FLIP;
33 import com.android.gallery3d.filtershow.presets.ImagePreset;
35 public abstract class ImageGeometry extends ImageShow {
36 protected boolean mVisibilityGained = false;
37 private boolean mHasDrawn = false;
39 protected static final float MAX_STRAIGHTEN_ANGLE = 45;
40 protected static final float MIN_STRAIGHTEN_ANGLE = -45;
42 protected float mCenterX;
43 protected float mCenterY;
45 protected float mCurrentX;
46 protected float mCurrentY;
47 protected float mTouchCenterX;
48 protected float mTouchCenterY;
50 // Local geometry data
51 private GeometryMetadata mLocalGeometry = null;
52 private RectF mLocalDisplayBounds = null;
53 protected float mXOffset = 0;
54 protected float mYOffset = 0;
56 protected enum MODES {
60 protected MODES mMode = MODES.NONE;
62 private static final String LOGTAG = "ImageGeometry";
64 public ImageGeometry(Context context, AttributeSet attrs) {
65 super(context, attrs);
68 public ImageGeometry(Context context) {
72 private void setupLocalDisplayBounds(RectF b) {
73 mLocalDisplayBounds = b;
74 calculateLocalScalingFactorAndOffset();
77 protected static float angleFor(float dx, float dy) {
78 return (float) (Math.atan2(dx, dy) * 180 / Math.PI);
81 protected static int snappedAngle(float angle) {
82 float remainder = angle % 90;
83 int current = (int) (angle / 90); // truncates
84 if (remainder < -45) {
86 } else if (remainder > 45) {
92 protected float getCurrentTouchAngle() {
93 if (mCurrentX == mTouchCenterX && mCurrentY == mTouchCenterY) {
96 float dX1 = mTouchCenterX - mCenterX;
97 float dY1 = mTouchCenterY - mCenterY;
98 float dX2 = mCurrentX - mCenterX;
99 float dY2 = mCurrentY - mCenterY;
101 float angleA = angleFor(dX1, dY1);
102 float angleB = angleFor(dX2, dY2);
103 return (angleB - angleA) % 360;
106 protected float computeScale(float width, float height) {
107 float imageWidth = mLocalGeometry.getPhotoBounds().width();
108 float imageHeight = mLocalGeometry.getPhotoBounds().height();
109 return GeometryMath.scale(imageWidth, imageHeight, width, height);
112 private void calculateLocalScalingFactorAndOffset() {
113 if (mLocalGeometry == null || mLocalDisplayBounds == null)
115 RectF imageBounds = mLocalGeometry.getPhotoBounds();
116 float imageWidth = imageBounds.width();
117 float imageHeight = imageBounds.height();
118 float displayWidth = mLocalDisplayBounds.width();
119 float displayHeight = mLocalDisplayBounds.height();
121 mCenterX = displayWidth / 2;
122 mCenterY = displayHeight / 2;
123 mYOffset = (displayHeight - imageHeight) / 2.0f;
124 mXOffset = (displayWidth - imageWidth) / 2.0f;
129 public void resetParameter() {
130 super.resetParameter();
132 setLocalStraighten(0);
133 setLocalCropBounds(getLocalPhotoBounds());
134 setLocalFlip(FLIP.NONE);
139 // Overwrites local with master
140 protected void syncLocalToMasterGeometry() {
141 mLocalGeometry = getGeometry();
142 calculateLocalScalingFactorAndOffset();
145 protected RectF getLocalPhotoBounds() {
146 return mLocalGeometry.getPhotoBounds();
149 protected RectF getLocalCropBounds() {
150 return mLocalGeometry.getPreviewCropBounds();
153 protected RectF getLocalDisplayBounds() {
154 return new RectF(mLocalDisplayBounds);
157 protected float getLocalScale() {
158 return mLocalGeometry.getScaleFactor();
161 protected float getLocalRotation() {
162 return mLocalGeometry.getRotation();
165 protected float getLocalStraighten() {
166 return mLocalGeometry.getStraightenRotation();
169 protected void setLocalScale(float s) {
170 mLocalGeometry.setScaleFactor(s);
173 protected void updateScale() {
174 RectF bounds = getUntranslatedStraightenCropBounds(mLocalGeometry.getPhotoBounds(),
175 getLocalStraighten());
176 float zoom = computeScale(bounds.width(), bounds.height());
180 protected void setLocalRotation(float r) {
181 mLocalGeometry.setRotation(r);
186 * Constrains rotation to be in [0, 90, 180, 270].
188 protected int constrainedRotation(float rotation) {
189 int r = (int) ((rotation % 360) / 90);
190 r = (r < 0) ? (r + 4) : r;
194 protected boolean isHeightWidthSwapped() {
195 return ((int) (getLocalRotation() / 90)) % 2 != 0;
198 protected void setLocalStraighten(float r) {
199 mLocalGeometry.setStraightenRotation(r);
203 protected void setLocalCropBounds(RectF c) {
204 mLocalGeometry.setCropBounds(c);
208 protected FLIP getLocalFlip() {
209 return mLocalGeometry.getFlipType();
212 protected void setLocalFlip(FLIP flip) {
213 mLocalGeometry.setFlipType(flip);
216 protected float getTotalLocalRotation() {
217 return getLocalRotation() + getLocalStraighten();
220 protected static Path drawClosedPath(Canvas canvas, Paint paint, float[] points) {
221 Path crop = new Path();
222 crop.moveTo(points[0], points[1]);
223 crop.lineTo(points[2], points[3]);
224 crop.lineTo(points[4], points[5]);
225 crop.lineTo(points[6], points[7]);
227 canvas.drawPath(crop, paint);
231 protected static float getNewHeightForWidthAspect(float width, float w, float h) {
232 return width * h / w;
235 protected static float getNewWidthForHeightAspect(float height, float w, float h) {
236 return height * w / h;
240 protected void onVisibilityChanged(View changedView, int visibility) {
241 super.onVisibilityChanged(changedView, visibility);
242 if (visibility == View.VISIBLE) {
243 mVisibilityGained = true;
244 syncLocalToMasterGeometry();
248 if (mVisibilityGained == true && mHasDrawn == true) {
251 mVisibilityGained = false;
256 protected void gainedVisibility() {
257 // Override this stub.
260 protected void lostVisibility() {
261 // Override this stub.
265 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
266 super.onSizeChanged(w, h, oldw, oldh);
267 setupLocalDisplayBounds(new RectF(0, 0, w, h));
271 public boolean onTouchEvent(MotionEvent event) {
272 switch (event.getActionMasked()) {
273 case (MotionEvent.ACTION_DOWN):
274 setActionDown(event.getX(), event.getY());
276 case (MotionEvent.ACTION_UP):
280 case (MotionEvent.ACTION_MOVE):
281 setActionMove(event.getX(), event.getY());
286 if (getPanelController() != null) {
287 getPanelController().onNewValue(getLocalValue());
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() {
320 public boolean showTitle() {
324 public String getName() {
328 public void saveAndSetPreset() {
329 ImagePreset lastHistoryItem = mMasterImage.getHistory().getLast();
330 if (lastHistoryItem != null && lastHistoryItem.historyName().equalsIgnoreCase(getName())) {
331 getImagePreset().setGeometry(mLocalGeometry);
332 resetImageCaches(this);
334 if (mLocalGeometry.hasModifications()) {
335 ImagePreset copy = new ImagePreset(getImagePreset());
336 copy.setGeometry(mLocalGeometry);
337 copy.setHistoryName(getName());
339 mMasterImage.setPreset(copy, true);
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 if (getDirtyGeometryFlag()) {
415 syncLocalToMasterGeometry();
416 clearDirtyGeometryFlag();
418 Bitmap image = getFiltersOnlyImage();
424 drawShape(canvas, image);
427 protected void drawShape(Canvas canvas, Bitmap image) {
428 // TODO: Override this stub.
432 * Sets up inputs for buildCenteredPhotoMatrix and buildWanderingCropMatrix
433 * and returns the scale factor.
435 protected float getTransformState(RectF photo, RectF crop, float[] displayCenter) {
436 RectF photoBounds = getLocalPhotoBounds();
437 RectF cropBounds = getLocalCropBounds();
438 float scale = computeScale(getWidth(), getHeight());
439 // checks if local rotation is an odd multiple of 90.
440 if (isHeightWidthSwapped()) {
441 scale = computeScale(getHeight(), getWidth());
443 // put in screen coordinates
445 crop.set(GeometryMath.scaleRect(cropBounds, scale));
448 photo.set(GeometryMath.scaleRect(photoBounds, scale));
450 if (displayCenter != null && displayCenter.length >= 2) {
451 displayCenter[0] = getWidth() / 2f;
452 displayCenter[1] = getHeight() / 2f;
457 protected RectF drawTransformed(Canvas canvas, Bitmap photo, Paint p, float[] offset) {
458 p.setARGB(255, 0, 0, 0);
459 float[] displayCenter = new float[2];
460 RectF scaledCrop = new RectF();
461 RectF scaledPhoto = new RectF();
462 float scale = getTransformState(scaledPhoto, scaledCrop, displayCenter);
463 Matrix m = GeometryMetadata.buildCenteredPhotoMatrix(scaledPhoto, scaledCrop,
464 getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter);
466 Matrix m1 = GeometryMetadata.buildWanderingCropMatrix(scaledPhoto, scaledCrop,
467 getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter);
468 m1.mapRect(scaledCrop);
469 Path path = new Path();
470 scaledCrop.offset(-offset[0], -offset[1]);
471 path.addRect(scaledCrop, Path.Direction.CCW);
473 m.preScale(scale, scale);
474 m.postTranslate(-offset[0], -offset[1]);
476 canvas.drawBitmap(photo, m, p);
479 p.setColor(Color.WHITE);
480 p.setStyle(Style.STROKE);
482 canvas.drawPath(path, p);
484 p.setColor(getDefaultBackgroundColor());
486 p.setStyle(Paint.Style.FILL);
487 drawShadows(canvas, p, scaledCrop);
491 protected void drawTransformedCropped(Canvas canvas, Bitmap photo, Paint p) {
492 RectF photoBounds = getLocalPhotoBounds();
493 RectF cropBounds = getLocalCropBounds();
494 float imageWidth = cropBounds.width();
495 float imageHeight = cropBounds.height();
496 float scale = GeometryMath.scale(imageWidth, imageHeight, getWidth(), getHeight());
497 // checks if local rotation is an odd multiple of 90.
498 if (isHeightWidthSwapped()) {
499 scale = GeometryMath.scale(imageWidth, imageHeight, getHeight(), getWidth());
501 // put in screen coordinates
502 RectF scaledCrop = GeometryMath.scaleRect(cropBounds, scale);
503 RectF scaledPhoto = GeometryMath.scaleRect(photoBounds, scale);
504 float[] displayCenter = {
505 getWidth() / 2f, getHeight() / 2f
507 Matrix m1 = GeometryMetadata.buildWanderingCropMatrix(scaledPhoto, scaledCrop,
508 getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter);
509 float[] cropCenter = {
510 scaledCrop.centerX(), scaledCrop.centerY()
512 m1.mapPoints(cropCenter);
513 GeometryMetadata.concatRecenterMatrix(m1, cropCenter, displayCenter);
514 m1.preRotate(getLocalStraighten(), scaledPhoto.centerX(), scaledPhoto.centerY());
515 m1.preScale(scale, scale);
517 p.setARGB(255, 0, 0, 0);
519 canvas.drawBitmap(photo, m1, p);
522 p.setColor(getDefaultBackgroundColor());
523 p.setStyle(Paint.Style.FILL);
524 scaledCrop.offset(displayCenter[0] - scaledCrop.centerX(), displayCenter[1]
525 - scaledCrop.centerY());
526 RectF display = new RectF(0, 0, getWidth(), getHeight());
527 drawRotatedShadows(canvas, p, scaledCrop, display, getLocalRotation(), getWidth() / 2,