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 ImageSlave {
36 private 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 = getMaster().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 Matrix getLocalGeoFlipMatrix(float width, float height) {
195 return mLocalGeometry.getFlipMatrix(width, height);
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 float[] getCornersFromRect(RectF r) {
229 r.right, r.bottom,// 2
230 r.left, r.bottom // 3
235 // If edge point [x, y] in array [x0, y0, x1, y1, ...] is outside of the
236 // image bound rectangle, clamps it to the edge of the rectangle.
237 protected static void getEdgePoints(RectF imageBound, float[] array) {
238 if (array.length < 2)
240 for (int x = 0; x < array.length; x += 2) {
241 array[x] = GeometryMath.clamp(array[x], imageBound.left, imageBound.right);
242 array[x + 1] = GeometryMath.clamp(array[x + 1], imageBound.top, imageBound.bottom);
246 protected static Path drawClosedPath(Canvas canvas, Paint paint, float[] points) {
247 Path crop = new Path();
248 crop.moveTo(points[0], points[1]);
249 crop.lineTo(points[2], points[3]);
250 crop.lineTo(points[4], points[5]);
251 crop.lineTo(points[6], points[7]);
253 canvas.drawPath(crop, paint);
257 protected static void fixAspectRatio(RectF r, float w, float h) {
258 float scale = Math.min(r.width() / w, r.height() / h);
259 float centX = r.centerX();
260 float centY = r.centerY();
261 float hw = scale * w / 2;
262 float hh = scale * h / 2;
263 r.set(centX - hw, centY - hh, centX + hw, centY + hh);
267 protected static float getNewHeightForWidthAspect(float width, float w, float h) {
268 return width * h / w;
271 protected static float getNewWidthForHeightAspect(float height, float w, float h) {
272 return height * w / h;
276 protected void onVisibilityChanged(View changedView, int visibility) {
277 super.onVisibilityChanged(changedView, visibility);
278 if (visibility == View.VISIBLE) {
279 mVisibilityGained = true;
280 syncLocalToMasterGeometry();
284 if (mVisibilityGained == true && mHasDrawn == true) {
287 mVisibilityGained = false;
292 protected void gainedVisibility() {
293 // TODO: Override this stub.
296 protected void lostVisibility() {
297 // TODO: Override this stub.
301 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
302 super.onSizeChanged(w, h, oldw, oldh);
303 setupLocalDisplayBounds(new RectF(0, 0, w, h));
307 public boolean onTouchEvent(MotionEvent event) {
308 switch (event.getActionMasked()) {
309 case (MotionEvent.ACTION_DOWN):
310 setActionDown(event.getX(), event.getY());
312 case (MotionEvent.ACTION_UP):
316 case (MotionEvent.ACTION_MOVE):
317 setActionMove(event.getX(), event.getY());
322 if (getPanelController() != null) {
323 getPanelController().onNewValue(getLocalValue());
329 protected int getLocalValue() {
330 return 0; // TODO: Override this
333 protected void setActionDown(float x, float y) {
341 protected void setActionMove(float x, float y) {
347 protected void setActionUp() {
351 protected void setNoAction() {
356 public boolean showTitle() {
360 public String getName() {
364 public void saveAndSetPreset() {
365 ImagePreset lastHistoryItem = getHistory().getLast();
366 if (lastHistoryItem != null && lastHistoryItem.historyName().equalsIgnoreCase(getName())) {
367 getImagePreset().setGeometry(mLocalGeometry);
368 resetImageCaches(this);
370 if (mLocalGeometry.hasModifications()) {
371 ImagePreset copy = new ImagePreset(getImagePreset());
372 copy.setGeometry(mLocalGeometry);
373 copy.setHistoryName(getName());
375 setImagePreset(copy, true);
381 public static RectF getUntranslatedStraightenCropBounds(RectF imageRect, float straightenAngle) {
382 float deg = straightenAngle;
386 double a = Math.toRadians(deg);
387 double sina = Math.sin(a);
388 double cosa = Math.cos(a);
390 double rw = imageRect.width();
391 double rh = imageRect.height();
392 double h1 = rh * rh / (rw * sina + rh * cosa);
393 double h2 = rh * rw / (rw * cosa + rh * sina);
394 double hh = Math.min(h1, h2);
395 double ww = hh * rw / rh;
397 float left = (float) ((rw - ww) * 0.5f);
398 float top = (float) ((rh - hh) * 0.5f);
399 float right = (float) (left + ww);
400 float bottom = (float) (top + hh);
402 return new RectF(left, top, right, bottom);
405 protected Matrix getGeoMatrix(RectF r, boolean onlyRotate) {
406 RectF pbounds = getLocalPhotoBounds();
407 float scale = GeometryMath
408 .scale(pbounds.width(), pbounds.height(), getWidth(), getHeight());
409 if (((int) (getLocalRotation() / 90)) % 2 != 0) {
410 scale = GeometryMath.scale(pbounds.width(), pbounds.height(), getHeight(), getWidth());
412 float yoff = getHeight() / 2;
413 float xoff = getWidth() / 2;
414 float w = r.left * 2 + r.width();
415 float h = r.top * 2 + r.height();
416 return mLocalGeometry.buildGeometryMatrix(w, h, scale, xoff, yoff, onlyRotate);
419 protected void drawImageBitmap(Canvas canvas, Bitmap bitmap, Paint paint, Matrix m) {
421 canvas.drawBitmap(bitmap, m, paint);
425 protected void drawImageBitmap(Canvas canvas, Bitmap bitmap, Paint paint) {
426 float scale = computeScale(getWidth(), getHeight());
427 float yoff = getHeight() / 2;
428 float xoff = getWidth() / 2;
429 Matrix m = mLocalGeometry.buildGeometryUIMatrix(scale, xoff, yoff);
430 drawImageBitmap(canvas, bitmap, paint, m);
433 protected RectF straightenBounds() {
434 RectF bounds = getUntranslatedStraightenCropBounds(getLocalPhotoBounds(),
435 getLocalStraighten());
436 Matrix m = getGeoMatrix(bounds, true);
441 protected void drawStraighten(Canvas canvas, Paint paint) {
442 RectF bounds = straightenBounds();
444 canvas.drawRect(bounds, paint);
448 protected RectF unrotatedCropBounds() {
449 RectF bounds = getLocalCropBounds();
450 RectF pbounds = getLocalPhotoBounds();
451 float scale = computeScale(getWidth(), getHeight());
452 float yoff = getHeight() / 2;
453 float xoff = getWidth() / 2;
454 Matrix m = mLocalGeometry.buildGeometryMatrix(pbounds.width(), pbounds.height(), scale,
460 protected RectF cropBounds() {
461 RectF bounds = getLocalCropBounds();
462 Matrix m = getGeoMatrix(getLocalPhotoBounds(), true);
467 // Fails for non-90 degree
468 protected void drawCrop(Canvas canvas, Paint paint) {
469 RectF bounds = cropBounds();
471 canvas.drawRect(bounds, paint);
475 protected void drawCropSafe(Canvas canvas, Paint paint) {
476 Matrix m = getGeoMatrix(getLocalPhotoBounds(), true);
477 RectF crop = getLocalCropBounds();
478 if (!m.rectStaysRect()) {
479 float[] corners = getCornersFromRect(crop);
480 m.mapPoints(corners);
481 drawClosedPath(canvas, paint, corners);
484 Path path = new Path();
485 path.addRect(crop, Path.Direction.CCW);
486 canvas.drawPath(path, paint);
490 protected void drawTransformedBitmap(Canvas canvas, Bitmap bitmap, Paint paint, boolean clip) {
491 paint.setARGB(255, 0, 0, 0);
492 drawImageBitmap(canvas, bitmap, paint);
493 paint.setColor(Color.WHITE);
494 paint.setStyle(Style.STROKE);
495 paint.setStrokeWidth(2);
496 drawCropSafe(canvas, paint);
497 paint.setColor(getDefaultBackgroundColor());
498 paint.setStyle(Paint.Style.FILL);
499 drawShadows(canvas, paint, unrotatedCropBounds());
502 protected void drawShadows(Canvas canvas, Paint p, RectF innerBounds) {
503 RectF display = new RectF(0, 0, getWidth(), getHeight());
504 drawShadows(canvas, p, innerBounds, display, getLocalRotation(), getWidth() / 2,
508 protected static void drawShadows(Canvas canvas, Paint p, RectF innerBounds, RectF outerBounds,
509 float rotation, float centerX, float centerY) {
511 canvas.rotate(rotation, centerX, centerY);
513 float x = (outerBounds.left - outerBounds.right);
514 float y = (outerBounds.top - outerBounds.bottom);
515 float longest = (float) Math.sqrt(x * x + y * y) / 2;
516 float minX = centerX - longest;
517 float maxX = centerX + longest;
518 float minY = centerY - longest;
519 float maxY = centerY + longest;
520 canvas.drawRect(minX, minY, innerBounds.right, innerBounds.top, p);
521 canvas.drawRect(minX, innerBounds.top, innerBounds.left, maxY, p);
522 canvas.drawRect(innerBounds.left, innerBounds.bottom, maxX, maxY,
524 canvas.drawRect(innerBounds.right, minY, maxX,
525 innerBounds.bottom, p);
526 canvas.rotate(-rotation, centerX, centerY);
531 public void onDraw(Canvas canvas) {
532 if (getDirtyGeometryFlag()) {
533 syncLocalToMasterGeometry();
534 clearDirtyGeometryFlag();
536 requestFilteredImages();
537 Bitmap image = getMaster().getFiltersOnlyImage();
543 drawShape(canvas, image);
546 protected void drawShape(Canvas canvas, Bitmap image) {
547 // TODO: Override this stub.
550 protected RectF drawTransformed(Canvas canvas, Bitmap photo, Paint p) {
551 p.setARGB(255, 0, 0, 0);
552 RectF photoBounds = getLocalPhotoBounds();
553 RectF cropBounds = getLocalCropBounds();
554 float scale = computeScale(getWidth(), getHeight());
555 // checks if local rotation is an odd multiple of 90.
556 if (((int) (getLocalRotation() / 90)) % 2 != 0) {
557 scale = computeScale(getHeight(), getWidth());
559 // put in screen coordinates
560 RectF scaledCrop = GeometryMath.scaleRect(cropBounds, scale);
561 RectF scaledPhoto = GeometryMath.scaleRect(photoBounds, scale);
562 float[] displayCenter = {
563 getWidth() / 2f, getHeight() / 2f
565 Matrix m = GeometryMetadata.buildCenteredPhotoMatrix(scaledPhoto, scaledCrop,
566 getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter);
568 Matrix m1 = GeometryMetadata.buildWanderingCropMatrix(scaledPhoto, scaledCrop,
569 getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter);
570 m1.mapRect(scaledCrop);
571 Path path = new Path();
572 path.addRect(scaledCrop, Path.Direction.CCW);
574 m.preScale(scale, scale);
576 canvas.drawBitmap(photo, m, p);
579 p.setColor(Color.WHITE);
580 p.setStyle(Style.STROKE);
582 canvas.drawPath(path, p);
586 protected void drawTransformedCropped(Canvas canvas, Bitmap photo, Paint p) {
587 RectF photoBounds = getLocalPhotoBounds();
588 RectF cropBounds = getLocalCropBounds();
589 float imageWidth = cropBounds.width();
590 float imageHeight = cropBounds.height();
591 float scale = GeometryMath.scale(imageWidth, imageHeight, getWidth(), getHeight());
592 // checks if local rotation is an odd multiple of 90.
593 if (((int) (getLocalRotation() / 90)) % 2 != 0) {
594 scale = GeometryMath.scale(imageWidth, imageHeight, getHeight(), getWidth());
596 // put in screen coordinates
597 RectF scaledCrop = GeometryMath.scaleRect(cropBounds, scale);
598 RectF scaledPhoto = GeometryMath.scaleRect(photoBounds, scale);
599 float[] displayCenter = {
600 getWidth() / 2f, getHeight() / 2f
602 Matrix m1 = GeometryMetadata.buildWanderingCropMatrix(scaledPhoto, scaledCrop,
603 getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter);
604 float[] cropCenter = {
605 scaledCrop.centerX(), scaledCrop.centerY()
607 m1.mapPoints(cropCenter);
608 GeometryMetadata.concatRecenterMatrix(m1, cropCenter, displayCenter);
609 m1.preRotate(getLocalStraighten(), scaledPhoto.centerX(), scaledPhoto.centerY());
610 m1.preScale(scale, scale);
612 p.setARGB(255, 0, 0, 0);
614 canvas.drawBitmap(photo, m1, p);
617 p.setColor(getDefaultBackgroundColor());
618 p.setStyle(Paint.Style.FILL);
619 scaledCrop.offset(displayCenter[0] - scaledCrop.centerX(), displayCenter[1]
620 - scaledCrop.centerY());
621 drawShadows(canvas, p, scaledCrop);