OSDN Git Service

Merge "Import translations. DO NOT MERGE" into gb-ub-photos-bryce
[android-x86/packages-apps-Gallery2.git] / src / com / android / gallery3d / filtershow / imageshow / ImageGeometry.java
1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package com.android.gallery3d.filtershow.imageshow;
18
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;
31
32 import com.android.gallery3d.filtershow.imageshow.GeometryMetadata.FLIP;
33 import com.android.gallery3d.filtershow.presets.ImagePreset;
34
35 public abstract class ImageGeometry extends ImageShow {
36     protected boolean mVisibilityGained = false;
37     private boolean mHasDrawn = false;
38
39     protected static final float MAX_STRAIGHTEN_ANGLE = 45;
40     protected static final float MIN_STRAIGHTEN_ANGLE = -45;
41
42     protected float mCenterX;
43     protected float mCenterY;
44
45     protected float mCurrentX;
46     protected float mCurrentY;
47     protected float mTouchCenterX;
48     protected float mTouchCenterY;
49
50     // Local geometry data
51     private GeometryMetadata mLocalGeometry = null;
52     private RectF mLocalDisplayBounds = null;
53     protected float mXOffset = 0;
54     protected float mYOffset = 0;
55
56     protected enum MODES {
57         NONE, DOWN, UP, MOVE
58     }
59
60     protected MODES mMode = MODES.NONE;
61
62     private static final String LOGTAG = "ImageGeometry";
63
64     public ImageGeometry(Context context, AttributeSet attrs) {
65         super(context, attrs);
66     }
67
68     public ImageGeometry(Context context) {
69         super(context);
70     }
71
72     private void setupLocalDisplayBounds(RectF b) {
73         mLocalDisplayBounds = b;
74         calculateLocalScalingFactorAndOffset();
75     }
76
77     protected static float angleFor(float dx, float dy) {
78         return (float) (Math.atan2(dx, dy) * 180 / Math.PI);
79     }
80
81     protected static int snappedAngle(float angle) {
82         float remainder = angle % 90;
83         int current = (int) (angle / 90); // truncates
84         if (remainder < -45) {
85             --current;
86         } else if (remainder > 45) {
87             ++current;
88         }
89         return current * 90;
90     }
91
92     protected float getCurrentTouchAngle() {
93         if (mCurrentX == mTouchCenterX && mCurrentY == mTouchCenterY) {
94             return 0;
95         }
96         float dX1 = mTouchCenterX - mCenterX;
97         float dY1 = mTouchCenterY - mCenterY;
98         float dX2 = mCurrentX - mCenterX;
99         float dY2 = mCurrentY - mCenterY;
100
101         float angleA = angleFor(dX1, dY1);
102         float angleB = angleFor(dX2, dY2);
103         return (angleB - angleA) % 360;
104     }
105
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);
110     }
111
112     private void calculateLocalScalingFactorAndOffset() {
113         if (mLocalGeometry == null || mLocalDisplayBounds == null)
114             return;
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();
120
121         mCenterX = displayWidth / 2;
122         mCenterY = displayHeight / 2;
123         mYOffset = (displayHeight - imageHeight) / 2.0f;
124         mXOffset = (displayWidth - imageWidth) / 2.0f;
125         updateScale();
126     }
127
128     @Override
129     public void resetParameter() {
130         super.resetParameter();
131         setLocalRotation(0);
132         setLocalStraighten(0);
133         setLocalCropBounds(getLocalPhotoBounds());
134         setLocalFlip(FLIP.NONE);
135         saveAndSetPreset();
136         invalidate();
137     }
138
139     // Overwrites local with master
140     protected void syncLocalToMasterGeometry() {
141         mLocalGeometry = getGeometry();
142         calculateLocalScalingFactorAndOffset();
143     }
144
145     protected RectF getLocalPhotoBounds() {
146         return mLocalGeometry.getPhotoBounds();
147     }
148
149     protected RectF getLocalCropBounds() {
150         return mLocalGeometry.getPreviewCropBounds();
151     }
152
153     protected RectF getLocalDisplayBounds() {
154         return new RectF(mLocalDisplayBounds);
155     }
156
157     protected float getLocalScale() {
158         return mLocalGeometry.getScaleFactor();
159     }
160
161     protected float getLocalRotation() {
162         return mLocalGeometry.getRotation();
163     }
164
165     protected float getLocalStraighten() {
166         return mLocalGeometry.getStraightenRotation();
167     }
168
169     protected void setLocalScale(float s) {
170         mLocalGeometry.setScaleFactor(s);
171     }
172
173     protected void updateScale() {
174         RectF bounds = getUntranslatedStraightenCropBounds(mLocalGeometry.getPhotoBounds(),
175                 getLocalStraighten());
176         float zoom = computeScale(bounds.width(), bounds.height());
177         setLocalScale(zoom);
178     }
179
180     protected void setLocalRotation(float r) {
181         mLocalGeometry.setRotation(r);
182         updateScale();
183     }
184
185     /**
186      * Constrains rotation to be in [0, 90, 180, 270].
187      */
188     protected int constrainedRotation(float rotation) {
189         int r = (int) ((rotation % 360) / 90);
190         r = (r < 0) ? (r + 4) : r;
191         return r * 90;
192     }
193
194     protected boolean isHeightWidthSwapped() {
195         return ((int) (getLocalRotation() / 90)) % 2 != 0;
196     }
197
198     protected void setLocalStraighten(float r) {
199         mLocalGeometry.setStraightenRotation(r);
200         updateScale();
201     }
202
203     protected void setLocalCropBounds(RectF c) {
204         mLocalGeometry.setCropBounds(c);
205         updateScale();
206     }
207
208     protected FLIP getLocalFlip() {
209         return mLocalGeometry.getFlipType();
210     }
211
212     protected void setLocalFlip(FLIP flip) {
213         mLocalGeometry.setFlipType(flip);
214     }
215
216     protected float getTotalLocalRotation() {
217         return getLocalRotation() + getLocalStraighten();
218     }
219
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]);
226         crop.close();
227         canvas.drawPath(crop, paint);
228         return crop;
229     }
230
231     protected static float getNewHeightForWidthAspect(float width, float w, float h) {
232         return width * h / w;
233     }
234
235     protected static float getNewWidthForHeightAspect(float height, float w, float h) {
236         return height * w / h;
237     }
238
239     @Override
240     protected void onVisibilityChanged(View changedView, int visibility) {
241         super.onVisibilityChanged(changedView, visibility);
242         if (visibility == View.VISIBLE) {
243             mVisibilityGained = true;
244             syncLocalToMasterGeometry();
245             updateScale();
246             gainedVisibility();
247         } else {
248             if (mVisibilityGained == true && mHasDrawn == true) {
249                 lostVisibility();
250             }
251             mVisibilityGained = false;
252             mHasDrawn = false;
253         }
254     }
255
256     protected void gainedVisibility() {
257         // Override this stub.
258     }
259
260     protected void lostVisibility() {
261         // Override this stub.
262     }
263
264     @Override
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));
268     }
269
270     @Override
271     public boolean onTouchEvent(MotionEvent event) {
272         switch (event.getActionMasked()) {
273             case (MotionEvent.ACTION_DOWN):
274                 setActionDown(event.getX(), event.getY());
275                 break;
276             case (MotionEvent.ACTION_UP):
277                 setActionUp();
278                 saveAndSetPreset();
279                 break;
280             case (MotionEvent.ACTION_MOVE):
281                 setActionMove(event.getX(), event.getY());
282                 break;
283             default:
284                 setNoAction();
285         }
286         if (getPanelController() != null) {
287             getPanelController().onNewValue(getLocalValue());
288         }
289         invalidate();
290         return true;
291     }
292
293     protected int getLocalValue() {
294         return 0; // Override this
295     }
296
297     protected void setActionDown(float x, float y) {
298         mTouchCenterX = x;
299         mTouchCenterY = y;
300         mCurrentX = x;
301         mCurrentY = y;
302         mMode = MODES.DOWN;
303     }
304
305     protected void setActionMove(float x, float y) {
306         mCurrentX = x;
307         mCurrentY = y;
308         mMode = MODES.MOVE;
309     }
310
311     protected void setActionUp() {
312         mMode = MODES.UP;
313     }
314
315     protected void setNoAction() {
316         mMode = MODES.NONE;
317     }
318
319     @Override
320     public boolean showTitle() {
321         return false;
322     }
323
324     public String getName() {
325         return "Geometry";
326     }
327
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);
333         } else {
334             if (mLocalGeometry.hasModifications()) {
335                 ImagePreset copy = new ImagePreset(getImagePreset());
336                 copy.setGeometry(mLocalGeometry);
337                 copy.setHistoryName(getName());
338                 copy.setIsFx(false);
339                 mMasterImage.setPreset(copy, true);
340             }
341         }
342         invalidate();
343     }
344
345     public static RectF getUntranslatedStraightenCropBounds(RectF imageRect, float straightenAngle) {
346         float deg = straightenAngle;
347         if (deg < 0) {
348             deg = -deg;
349         }
350         double a = Math.toRadians(deg);
351         double sina = Math.sin(a);
352         double cosa = Math.cos(a);
353
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;
360
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);
365
366         return new RectF(left, top, right, bottom);
367     }
368
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);
377         return bounds;
378     }
379
380     protected static void drawRotatedShadows(Canvas canvas, Paint p, RectF innerBounds,
381             RectF outerBounds,
382             float rotation, float centerX, float centerY) {
383         canvas.save();
384         canvas.rotate(rotation, centerX, centerY);
385
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,
396                 p);
397         canvas.drawRect(innerBounds.right, minY, maxX,
398                 innerBounds.bottom, p);
399         canvas.rotate(-rotation, centerX, centerY);
400         canvas.restore();
401     }
402
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);
410     }
411
412     @Override
413     public void onDraw(Canvas canvas) {
414         if (getDirtyGeometryFlag()) {
415             syncLocalToMasterGeometry();
416             clearDirtyGeometryFlag();
417         }
418         Bitmap image = getFiltersOnlyImage();
419         if (image == null) {
420             invalidate();
421             return;
422         }
423         mHasDrawn = true;
424         drawShape(canvas, image);
425     }
426
427     protected void drawShape(Canvas canvas, Bitmap image) {
428         // TODO: Override this stub.
429     }
430
431     /**
432      * Sets up inputs for buildCenteredPhotoMatrix and buildWanderingCropMatrix
433      * and returns the scale factor.
434      */
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());
442         }
443         // put in screen coordinates
444         if (crop != null) {
445             crop.set(GeometryMath.scaleRect(cropBounds, scale));
446         }
447         if (photo != null) {
448             photo.set(GeometryMath.scaleRect(photoBounds, scale));
449         }
450         if (displayCenter != null && displayCenter.length >= 2) {
451             displayCenter[0] = getWidth() / 2f;
452             displayCenter[1] = getHeight() / 2f;
453         }
454         return scale;
455     }
456
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);
465
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);
472
473         m.preScale(scale, scale);
474         m.postTranslate(-offset[0], -offset[1]);
475         canvas.save();
476         canvas.drawBitmap(photo, m, p);
477         canvas.restore();
478
479         p.setColor(Color.WHITE);
480         p.setStyle(Style.STROKE);
481         p.setStrokeWidth(2);
482         canvas.drawPath(path, p);
483
484         p.setColor(getDefaultBackgroundColor());
485         p.setAlpha(128);
486         p.setStyle(Paint.Style.FILL);
487         drawShadows(canvas, p, scaledCrop);
488         return scaledCrop;
489     }
490
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());
500         }
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
506         };
507         Matrix m1 = GeometryMetadata.buildWanderingCropMatrix(scaledPhoto, scaledCrop,
508                 getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter);
509         float[] cropCenter = {
510                 scaledCrop.centerX(), scaledCrop.centerY()
511         };
512         m1.mapPoints(cropCenter);
513         GeometryMetadata.concatRecenterMatrix(m1, cropCenter, displayCenter);
514         m1.preRotate(getLocalStraighten(), scaledPhoto.centerX(), scaledPhoto.centerY());
515         m1.preScale(scale, scale);
516
517         p.setARGB(255, 0, 0, 0);
518         canvas.save();
519         canvas.drawBitmap(photo, m1, p);
520         canvas.restore();
521
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,
528                 getHeight() / 2);
529     }
530 }