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 float zoom = width / imageWidth;
110 if (imageHeight > imageWidth) {
111 zoom = height / imageHeight;
116 private void calculateLocalScalingFactorAndOffset() {
117 if (mLocalGeometry == null || mLocalDisplayBounds == null)
119 RectF imageBounds = mLocalGeometry.getPhotoBounds();
120 float imageWidth = imageBounds.width();
121 float imageHeight = imageBounds.height();
122 float displayWidth = mLocalDisplayBounds.width();
123 float displayHeight = mLocalDisplayBounds.height();
125 mCenterX = displayWidth / 2;
126 mCenterY = displayHeight / 2;
127 mYOffset = (displayHeight - imageHeight) / 2.0f;
128 mXOffset = (displayWidth - imageWidth) / 2.0f;
133 public void resetParameter() {
134 super.resetParameter();
136 setLocalStraighten(0);
137 setLocalCropBounds(getLocalPhotoBounds());
138 setLocalFlip(FLIP.NONE);
143 // Overwrites local with master
144 protected void syncLocalToMasterGeometry() {
145 mLocalGeometry = getMaster().getGeometry();
146 calculateLocalScalingFactorAndOffset();
149 protected RectF getLocalPhotoBounds() {
150 return mLocalGeometry.getPhotoBounds();
153 protected RectF getLocalCropBounds() {
154 return mLocalGeometry.getPreviewCropBounds();
157 protected RectF getLocalDisplayBounds() {
158 return new RectF(mLocalDisplayBounds);
161 protected float getLocalScale() {
162 return mLocalGeometry.getScaleFactor();
165 protected float getLocalRotation() {
166 return mLocalGeometry.getRotation();
169 protected float getLocalStraighten() {
170 return mLocalGeometry.getStraightenRotation();
173 protected void setLocalScale(float s) {
174 mLocalGeometry.setScaleFactor(s);
177 protected void updateScale() {
178 RectF bounds = getUntranslatedStraightenCropBounds(mLocalGeometry.getPhotoBounds(),
179 getLocalStraighten());
180 float zoom = computeScale(bounds.width(), bounds.height());
184 protected void setLocalRotation(float r) {
185 mLocalGeometry.setRotation(r);
190 * Constrains rotation to be in [0, 90, 180, 270].
192 protected int constrainedRotation(float rotation) {
193 int r = (int) ((rotation % 360) / 90);
194 r = (r < 0) ? (r + 4) : r;
198 protected Matrix getLocalGeoFlipMatrix(float width, float height) {
199 return mLocalGeometry.getFlipMatrix(width, height);
202 protected void setLocalStraighten(float r) {
203 mLocalGeometry.setStraightenRotation(r);
207 protected void setLocalCropBounds(RectF c) {
208 mLocalGeometry.setCropBounds(c);
212 protected FLIP getLocalFlip() {
213 return mLocalGeometry.getFlipType();
216 protected void setLocalFlip(FLIP flip) {
217 mLocalGeometry.setFlipType(flip);
220 protected float getTotalLocalRotation() {
221 return getLocalRotation() + getLocalStraighten();
225 protected static float[] getCornersFromRect(RectF r) {
234 r.right, r.bottom,// 2
235 r.left, r.bottom // 3
240 // If edge point [x, y] in array [x0, y0, x1, y1, ...] is outside of the
241 // image bound rectangle, clamps it to the edge of the rectangle.
242 protected static void getEdgePoints(RectF imageBound, float[] array) {
243 if (array.length < 2)
245 for (int x = 0; x < array.length; x += 2) {
246 array[x] = GeometryMath.clamp(array[x], imageBound.left, imageBound.right);
247 array[x + 1] = GeometryMath.clamp(array[x + 1], imageBound.top, imageBound.bottom);
251 protected static Path drawClosedPath(Canvas canvas, Paint paint, float[] points) {
252 Path crop = new Path();
253 crop.moveTo(points[0], points[1]);
254 crop.lineTo(points[2], points[3]);
255 crop.lineTo(points[4], points[5]);
256 crop.lineTo(points[6], points[7]);
258 canvas.drawPath(crop, paint);
262 protected static void fixAspectRatio(RectF r, float w, float h) {
263 float scale = Math.min(r.width() / w, r.height() / h);
264 float centX = r.centerX();
265 float centY = r.centerY();
266 float hw = scale * w / 2;
267 float hh = scale * h / 2;
268 r.set(centX - hw, centY - hh, centX + hw, centY + hh);
272 protected static float getNewHeightForWidthAspect(float width, float w, float h) {
273 return width * h / w;
276 protected static float getNewWidthForHeightAspect(float height, float w, float h) {
277 return height * w / h;
281 protected void onVisibilityChanged(View changedView, int visibility) {
282 super.onVisibilityChanged(changedView, visibility);
283 if (visibility == View.VISIBLE) {
284 mVisibilityGained = true;
285 syncLocalToMasterGeometry();
289 if (mVisibilityGained == true && mHasDrawn == true) {
292 mVisibilityGained = false;
297 protected void gainedVisibility() {
298 // TODO: Override this stub.
301 protected void lostVisibility() {
302 // TODO: Override this stub.
306 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
307 super.onSizeChanged(w, h, oldw, oldh);
308 setupLocalDisplayBounds(new RectF(0, 0, w, h));
312 public boolean onTouchEvent(MotionEvent event) {
313 switch (event.getActionMasked()) {
314 case (MotionEvent.ACTION_DOWN):
315 setActionDown(event.getX(), event.getY());
317 case (MotionEvent.ACTION_UP):
321 case (MotionEvent.ACTION_MOVE):
322 setActionMove(event.getX(), event.getY());
327 if (getPanelController() != null) {
328 getPanelController().onNewValue(getLocalValue());
334 protected int getLocalValue() {
335 return 0; // TODO: Override this
338 protected void setActionDown(float x, float y) {
346 protected void setActionMove(float x, float y) {
352 protected void setActionUp() {
356 protected void setNoAction() {
361 public boolean showTitle() {
365 public String getName() {
369 protected void saveAndSetPreset() {
370 ImagePreset lastHistoryItem = getHistory().getLast();
371 if (lastHistoryItem != null && lastHistoryItem.historyName().equalsIgnoreCase(getName())) {
372 getImagePreset().setGeometry(mLocalGeometry);
373 resetImageCaches(this);
375 ImagePreset copy = new ImagePreset(getImagePreset());
376 copy.setGeometry(mLocalGeometry);
377 copy.setHistoryName(getName());
379 setImagePreset(copy, true);
384 public static RectF getUntranslatedStraightenCropBounds(RectF imageRect, float straightenAngle) {
385 float deg = straightenAngle;
389 double a = Math.toRadians(deg);
390 double sina = Math.sin(a);
391 double cosa = Math.cos(a);
393 double rw = imageRect.width();
394 double rh = imageRect.height();
395 double h1 = rh * rh / (rw * sina + rh * cosa);
396 double h2 = rh * rw / (rw * cosa + rh * sina);
397 double hh = Math.min(h1, h2);
398 double ww = hh * rw / rh;
400 float left = (float) ((rw - ww) * 0.5f);
401 float top = (float) ((rh - hh) * 0.5f);
402 float right = (float) (left + ww);
403 float bottom = (float) (top + hh);
405 return new RectF(left, top, right, bottom);
408 protected Matrix getGeoMatrix(RectF r, boolean onlyRotate) {
409 float scale = computeScale(getWidth(), getHeight());
410 float yoff = getHeight() / 2;
411 float xoff = getWidth() / 2;
412 float w = r.left * 2 + r.width();
413 float h = r.top * 2 + r.height();
414 return mLocalGeometry.buildGeometryMatrix(w, h, scale, xoff, yoff, onlyRotate);
417 protected void drawImageBitmap(Canvas canvas, Bitmap bitmap, Paint paint, Matrix m) {
419 canvas.drawBitmap(bitmap, m, paint);
423 protected void drawImageBitmap(Canvas canvas, Bitmap bitmap, Paint paint) {
424 float scale = computeScale(getWidth(), getHeight());
425 float yoff = getHeight() / 2;
426 float xoff = getWidth() / 2;
427 Matrix m = mLocalGeometry.buildGeometryUIMatrix(scale, xoff, yoff);
428 drawImageBitmap(canvas, bitmap, paint, m);
431 protected RectF straightenBounds() {
432 RectF bounds = getUntranslatedStraightenCropBounds(getLocalPhotoBounds(),
433 getLocalStraighten());
434 Matrix m = getGeoMatrix(bounds, true);
439 protected void drawStraighten(Canvas canvas, Paint paint) {
440 RectF bounds = straightenBounds();
442 canvas.drawRect(bounds, paint);
446 protected RectF unrotatedCropBounds() {
447 RectF bounds = getLocalCropBounds();
448 RectF pbounds = getLocalPhotoBounds();
449 float scale = computeScale(getWidth(), getHeight());
450 float yoff = getHeight() / 2;
451 float xoff = getWidth() / 2;
452 Matrix m = mLocalGeometry.buildGeometryMatrix(pbounds.width(), pbounds.height(), scale, xoff, yoff, 0);
457 protected RectF cropBounds() {
458 RectF bounds = getLocalCropBounds();
459 Matrix m = getGeoMatrix(getLocalPhotoBounds(), true);
464 // Fails for non-90 degree
465 protected void drawCrop(Canvas canvas, Paint paint) {
466 RectF bounds = cropBounds();
468 canvas.drawRect(bounds, paint);
472 protected void drawCropSafe(Canvas canvas, Paint paint) {
473 Matrix m = getGeoMatrix(getLocalPhotoBounds(), true);
474 RectF crop = getLocalCropBounds();
475 if (!m.rectStaysRect()) {
476 float[] corners = getCornersFromRect(crop);
477 m.mapPoints(corners);
478 drawClosedPath(canvas, paint, corners);
481 Path path = new Path();
482 path.addRect(crop, Path.Direction.CCW);
483 canvas.drawPath(path, paint);
487 protected void drawTransformedBitmap(Canvas canvas, Bitmap bitmap, Paint paint, boolean clip) {
488 paint.setARGB(255, 0, 0, 0);
489 drawImageBitmap(canvas, bitmap, paint);
490 paint.setColor(Color.WHITE);
491 paint.setStyle(Style.STROKE);
492 paint.setStrokeWidth(2);
493 drawCropSafe(canvas, paint);
494 paint.setARGB(128, 0, 0, 0);
495 paint.setStyle(Paint.Style.FILL);
496 drawShadows(canvas, paint, unrotatedCropBounds());
499 protected void drawShadows(Canvas canvas, Paint p, RectF innerBounds) {
500 RectF display = new RectF(0, 0, getWidth(), getHeight());
501 drawShadows(canvas, p, innerBounds, display, getLocalRotation(), getWidth() / 2,
505 protected static void drawShadows(Canvas canvas, Paint p, RectF innerBounds, RectF outerBounds,
506 float rotation, float centerX, float centerY) {
508 canvas.rotate(rotation, centerX, centerY);
510 float x = (outerBounds.left - outerBounds.right);
511 float y = (outerBounds.top - outerBounds.bottom);
512 float longest = (float) Math.sqrt(x * x + y * y) / 2;
513 float minX = centerX - longest;
514 float maxX = centerX + longest;
515 float minY = centerY - longest;
516 float maxY = centerY + longest;
517 canvas.drawRect(minX, minY, innerBounds.right, innerBounds.top, p);
518 canvas.drawRect(minX, innerBounds.top, innerBounds.left, maxY, p);
519 canvas.drawRect(innerBounds.left, innerBounds.bottom, maxX, maxY,
521 canvas.drawRect(innerBounds.right, minY, maxX,
522 innerBounds.bottom, p);
523 canvas.rotate(-rotation, centerX, centerY);
528 public void onDraw(Canvas canvas) {
529 if (getDirtyGeometryFlag()) {
530 syncLocalToMasterGeometry();
531 clearDirtyGeometryFlag();
533 requestFilteredImages();
534 Bitmap image = getMaster().getFiltersOnlyImage();
540 drawShape(canvas, image);
543 protected void drawShape(Canvas canvas, Bitmap image) {
544 // TODO: Override this stub.
547 protected RectF drawTransformed(Canvas canvas, Bitmap photo, Paint p){
548 p.setARGB(255, 0, 0, 0);
549 RectF photoBounds = getLocalPhotoBounds();
550 RectF cropBounds = getLocalCropBounds();
551 float scale = computeScale(getWidth(), getHeight());
552 // put in screen coordinates
553 RectF scaledCrop = GeometryMath.scaleRect(cropBounds, scale);
554 RectF scaledPhoto = GeometryMath.scaleRect(photoBounds, scale);
555 float [] displayCenter = { getWidth() / 2f, getHeight() / 2f };
556 Matrix m = GeometryMetadata.buildCenteredPhotoMatrix(scaledPhoto, scaledCrop,
557 getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter);
559 Matrix m1 = GeometryMetadata.buildWanderingCropMatrix(scaledPhoto, scaledCrop,
560 getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter);
561 m1.mapRect(scaledCrop);
562 Path path = new Path();
563 path.addRect(scaledCrop, Path.Direction.CCW);
565 m.preScale(scale, scale);
567 canvas.drawBitmap(photo, m, p);
570 p.setColor(Color.WHITE);
571 p.setStyle(Style.STROKE);
573 canvas.drawPath(path, p);
577 protected void drawTransformedCropped(Canvas canvas, Bitmap photo, Paint p){
578 RectF photoBounds = getLocalPhotoBounds();
579 RectF cropBounds = getLocalCropBounds();
580 float imageWidth = cropBounds.width();
581 float imageHeight = cropBounds.height();
582 float scale = getWidth() / imageWidth;
583 if (imageHeight > imageWidth) {
584 scale = getHeight() / imageHeight;
586 // put in screen coordinates
587 RectF scaledCrop = GeometryMath.scaleRect(cropBounds, scale);
588 RectF scaledPhoto = GeometryMath.scaleRect(photoBounds, scale);
589 float [] displayCenter = { getWidth() / 2f, getHeight() / 2f };
590 Matrix m1 = GeometryMetadata.buildWanderingCropMatrix(scaledPhoto, scaledCrop,
591 getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter);
592 float [] cropCenter = { scaledCrop.centerX(), scaledCrop.centerY() };
593 m1.mapPoints(cropCenter);
594 GeometryMetadata.concatRecenterMatrix(m1, cropCenter, displayCenter);
595 m1.preRotate(getLocalStraighten(), scaledPhoto.centerX(), scaledPhoto.centerY());
596 m1.preScale(scale, scale);
598 p.setARGB(255, 0, 0, 0);
600 canvas.drawBitmap(photo, m1, p);
603 p.setARGB(255, 0, 0, 0);
604 p.setStyle(Paint.Style.FILL);
605 scaledCrop.offset(displayCenter[0] - scaledCrop.centerX(), displayCenter[1]
606 - scaledCrop.centerY());
607 drawShadows(canvas, p, scaledCrop);