OSDN Git Service

Fix problem with N-1 caching when no filters / one filter
[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.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;
36
37 public abstract class ImageGeometry extends ImageShow {
38     protected boolean mVisibilityGained = false;
39     private boolean mHasDrawn = false;
40
41     protected static final float MAX_STRAIGHTEN_ANGLE = 45;
42     protected static final float MIN_STRAIGHTEN_ANGLE = -45;
43
44     protected float mCenterX;
45     protected float mCenterY;
46
47     protected float mCurrentX;
48     protected float mCurrentY;
49     protected float mTouchCenterX;
50     protected float mTouchCenterY;
51
52     // Local geometry data
53     private GeometryMetadata mLocalGeometry = null;
54     private RectF mLocalDisplayBounds = null;
55     protected float mXOffset = 0;
56     protected float mYOffset = 0;
57
58     protected enum MODES {
59         NONE, DOWN, UP, MOVE
60     }
61
62     protected MODES mMode = MODES.NONE;
63
64     private static final String LOGTAG = "ImageGeometry";
65
66     public ImageGeometry(Context context, AttributeSet attrs) {
67         super(context, attrs);
68     }
69
70     public ImageGeometry(Context context) {
71         super(context);
72     }
73
74     private void setupLocalDisplayBounds(RectF b) {
75         mLocalDisplayBounds = b;
76         calculateLocalScalingFactorAndOffset();
77     }
78
79     protected static float angleFor(float dx, float dy) {
80         return (float) (Math.atan2(dx, dy) * 180 / Math.PI);
81     }
82
83     protected static int snappedAngle(float angle) {
84         float remainder = angle % 90;
85         int current = (int) (angle / 90); // truncates
86         if (remainder < -45) {
87             --current;
88         } else if (remainder > 45) {
89             ++current;
90         }
91         return current * 90;
92     }
93
94     protected float getCurrentTouchAngle() {
95         if (mCurrentX == mTouchCenterX && mCurrentY == mTouchCenterY) {
96             return 0;
97         }
98         float dX1 = mTouchCenterX - mCenterX;
99         float dY1 = mTouchCenterY - mCenterY;
100         float dX2 = mCurrentX - mCenterX;
101         float dY2 = mCurrentY - mCenterY;
102
103         float angleA = angleFor(dX1, dY1);
104         float angleB = angleFor(dX2, dY2);
105         return (angleB - angleA) % 360;
106     }
107
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);
112     }
113
114     private void calculateLocalScalingFactorAndOffset() {
115         if (mLocalGeometry == null || mLocalDisplayBounds == null)
116             return;
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();
122
123         mCenterX = displayWidth / 2;
124         mCenterY = displayHeight / 2;
125         mYOffset = (displayHeight - imageHeight) / 2.0f;
126         mXOffset = (displayWidth - imageWidth) / 2.0f;
127         updateScale();
128     }
129
130     @Override
131     public void resetParameter() {
132         super.resetParameter();
133         setLocalRotation(0);
134         setLocalStraighten(0);
135         setLocalCropBounds(getLocalPhotoBounds());
136         setLocalMirror(Mirror.NONE);
137         saveAndSetPreset();
138         invalidate();
139     }
140
141     // Overwrites local with master
142     public void syncLocalToMasterGeometry() {
143         mLocalGeometry = getGeometry();
144         calculateLocalScalingFactorAndOffset();
145     }
146
147     protected RectF getLocalPhotoBounds() {
148         return mLocalGeometry.getPhotoBounds();
149     }
150
151     protected RectF getLocalCropBounds() {
152         return mLocalGeometry.getPreviewCropBounds();
153     }
154
155     protected RectF getLocalDisplayBounds() {
156         return new RectF(mLocalDisplayBounds);
157     }
158
159     protected float getLocalScale() {
160         return mLocalGeometry.getScaleFactor();
161     }
162
163     protected float getLocalRotation() {
164         return mLocalGeometry.getRotation();
165     }
166
167     protected float getLocalStraighten() {
168         return mLocalGeometry.getStraightenRotation();
169     }
170
171     protected void setLocalScale(float s) {
172         mLocalGeometry.setScaleFactor(s);
173     }
174
175     protected void updateScale() {
176         RectF bounds = getUntranslatedStraightenCropBounds(mLocalGeometry.getPhotoBounds(),
177                 getLocalStraighten());
178         float zoom = computeScale(bounds.width(), bounds.height());
179         setLocalScale(zoom);
180     }
181
182     protected void setLocalRotation(int r) {
183         mLocalGeometry.setRotation(r);
184         updateScale();
185     }
186
187     /**
188      * Constrains rotation to be in [0, 90, 180, 270].
189      */
190     protected int constrainedRotation(float rotation) {
191         int r = (int) ((rotation % 360) / 90);
192         r = (r < 0) ? (r + 4) : r;
193         return r * 90;
194     }
195
196     protected boolean isHeightWidthSwapped() {
197         return ((int) (getLocalRotation() / 90)) % 2 != 0;
198     }
199
200     protected void setLocalStraighten(float r) {
201         mLocalGeometry.setStraightenRotation(r);
202         updateScale();
203     }
204
205     protected void setLocalCropBounds(RectF c) {
206         mLocalGeometry.setCropBounds(c);
207         updateScale();
208     }
209
210     protected Mirror getLocalMirror() {
211         return mLocalGeometry.getMirrorType();
212     }
213
214     protected void setLocalMirror(Mirror flip) {
215         mLocalGeometry.setMirrorType(flip);
216     }
217
218     protected float getTotalLocalRotation() {
219         return getLocalRotation() + getLocalStraighten();
220     }
221
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]);
228         crop.close();
229         canvas.drawPath(crop, paint);
230         return crop;
231     }
232
233     protected static float getNewHeightForWidthAspect(float width, float w, float h) {
234         return width * h / w;
235     }
236
237     protected static float getNewWidthForHeightAspect(float height, float w, float h) {
238         return height * w / h;
239     }
240
241     @Override
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();
248             updateScale();
249             gainedVisibility();
250         } else {
251             if (mVisibilityGained == true && mHasDrawn == true) {
252                 lostVisibility();
253             }
254             mVisibilityGained = false;
255             mHasDrawn = false;
256         }
257     }
258
259     protected void gainedVisibility() {
260         // Override this stub.
261     }
262
263     protected void lostVisibility() {
264         // Override this stub.
265     }
266
267     @Override
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));
271     }
272
273     @Override
274     public boolean onTouchEvent(MotionEvent event) {
275         switch (event.getActionMasked()) {
276             case (MotionEvent.ACTION_DOWN):
277                 setActionDown(event.getX(), event.getY());
278                 break;
279             case (MotionEvent.ACTION_UP):
280                 setActionUp();
281                 saveAndSetPreset();
282                 break;
283             case (MotionEvent.ACTION_MOVE):
284                 setActionMove(event.getX(), event.getY());
285                 break;
286             default:
287                 setNoAction();
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     public void saveAndSetPreset() {
320         FilterRepresentation lastAction = null;
321         HistoryItem historyItem = MasterImage.getImage().getHistory().getLast();
322         if (historyItem != null) {
323             lastAction = historyItem.getFilterRepresentation();
324         }
325
326         if (lastAction != null
327                 && lastAction.getSerializationName().equalsIgnoreCase(
328                         GeometryMetadata.SERIALIZATION_NAME)) {
329             getImagePreset().setGeometry(mLocalGeometry);
330             resetImageCaches(this);
331         } else {
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);
339             }
340         }
341         MasterImage.getImage().notifyGeometryChange();
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         Bitmap image = getFiltersOnlyImage();
415         if (image == null) {
416             invalidate();
417             return;
418         }
419         mHasDrawn = true;
420
421         drawShape(canvas, image);
422     }
423
424     protected void drawShape(Canvas canvas, Bitmap image) {
425         // TODO: Override this stub.
426     }
427
428     /**
429      * Sets up inputs for buildCenteredPhotoMatrix and buildWanderingCropMatrix
430      * and returns the scale factor.
431      */
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());
439         }
440         // put in screen coordinates
441         if (crop != null) {
442             crop.set(GeometryMath.scaleRect(cropBounds, scale));
443         }
444         if (photo != null) {
445             photo.set(GeometryMath.scaleRect(photoBounds, scale));
446         }
447         if (displayCenter != null && displayCenter.length >= 2) {
448             displayCenter[0] = getWidth() / 2f;
449             displayCenter[1] = getHeight() / 2f;
450         }
451         return scale;
452     }
453
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);
462
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);
469
470         m.preScale(scale, scale);
471         m.postTranslate(-offset[0], -offset[1]);
472         canvas.save();
473         canvas.drawBitmap(photo, m, p);
474         canvas.restore();
475
476         p.setColor(Color.WHITE);
477         p.setStyle(Style.STROKE);
478         p.setStrokeWidth(2);
479         canvas.drawPath(path, p);
480
481         p.setColor(mBackgroundColor);
482         p.setAlpha(128);
483         p.setStyle(Paint.Style.FILL);
484         drawShadows(canvas, p, scaledCrop);
485         return scaledCrop;
486     }
487
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());
497         }
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
503         };
504         Matrix m1 = GeometryMetadata.buildWanderingCropMatrix(scaledPhoto, scaledCrop,
505                 getLocalRotation(), getLocalStraighten(), getLocalMirror(), displayCenter);
506         float[] cropCenter = {
507                 scaledCrop.centerX(), scaledCrop.centerY()
508         };
509         m1.mapPoints(cropCenter);
510         GeometryMetadata.concatRecenterMatrix(m1, cropCenter, displayCenter);
511         m1.preRotate(getLocalStraighten(), scaledPhoto.centerX(), scaledPhoto.centerY());
512         m1.preScale(scale, scale);
513
514         p.setARGB(255, 0, 0, 0);
515         canvas.save();
516         canvas.drawBitmap(photo, m1, p);
517         canvas.restore();
518
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,
525                 getHeight() / 2);
526     }
527 }