OSDN Git Service

Reworked crop/transform UI.
[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 ImageSlave {
36     private 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         float zoom = width / imageWidth;
110         if (imageHeight > imageWidth) {
111             zoom = height / imageHeight;
112         }
113         return zoom;
114     }
115
116     private void calculateLocalScalingFactorAndOffset() {
117         if (mLocalGeometry == null || mLocalDisplayBounds == null)
118             return;
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();
124
125         mCenterX = displayWidth / 2;
126         mCenterY = displayHeight / 2;
127         mYOffset = (displayHeight - imageHeight) / 2.0f;
128         mXOffset = (displayWidth - imageWidth) / 2.0f;
129         updateScale();
130     }
131
132     @Override
133     public void resetParameter() {
134         super.resetParameter();
135         setLocalRotation(0);
136         setLocalStraighten(0);
137         setLocalCropBounds(getLocalPhotoBounds());
138         setLocalFlip(FLIP.NONE);
139         saveAndSetPreset();
140         invalidate();
141     }
142
143     // Overwrites local with master
144     protected void syncLocalToMasterGeometry() {
145         mLocalGeometry = getMaster().getGeometry();
146         calculateLocalScalingFactorAndOffset();
147     }
148
149     protected RectF getLocalPhotoBounds() {
150         return mLocalGeometry.getPhotoBounds();
151     }
152
153     protected RectF getLocalCropBounds() {
154         return mLocalGeometry.getPreviewCropBounds();
155     }
156
157     protected RectF getLocalDisplayBounds() {
158         return new RectF(mLocalDisplayBounds);
159     }
160
161     protected float getLocalScale() {
162         return mLocalGeometry.getScaleFactor();
163     }
164
165     protected float getLocalRotation() {
166         return mLocalGeometry.getRotation();
167     }
168
169     protected float getLocalStraighten() {
170         return mLocalGeometry.getStraightenRotation();
171     }
172
173     protected void setLocalScale(float s) {
174         mLocalGeometry.setScaleFactor(s);
175     }
176
177     protected void updateScale() {
178         RectF bounds = getUntranslatedStraightenCropBounds(mLocalGeometry.getPhotoBounds(),
179                 getLocalStraighten());
180         float zoom = computeScale(bounds.width(), bounds.height());
181         setLocalScale(zoom);
182     }
183
184     protected void setLocalRotation(float r) {
185         mLocalGeometry.setRotation(r);
186         updateScale();
187     }
188
189     /**
190      * Constrains rotation to be in [0, 90, 180, 270].
191      */
192     protected int constrainedRotation(float rotation) {
193         int r = (int) ((rotation % 360) / 90);
194         r = (r < 0) ? (r + 4) : r;
195         return r * 90;
196     }
197
198     protected Matrix getLocalGeoFlipMatrix(float width, float height) {
199         return mLocalGeometry.getFlipMatrix(width, height);
200     }
201
202     protected void setLocalStraighten(float r) {
203         mLocalGeometry.setStraightenRotation(r);
204         updateScale();
205     }
206
207     protected void setLocalCropBounds(RectF c) {
208         mLocalGeometry.setCropBounds(c);
209         updateScale();
210     }
211
212     protected FLIP getLocalFlip() {
213         return mLocalGeometry.getFlipType();
214     }
215
216     protected void setLocalFlip(FLIP flip) {
217         mLocalGeometry.setFlipType(flip);
218     }
219
220     protected float getTotalLocalRotation() {
221         return getLocalRotation() + getLocalStraighten();
222     }
223
224
225     protected static float[] getCornersFromRect(RectF r) {
226         // Order is:
227         // 0------->1
228         // ^        |
229         // |        v
230         // 3<-------2
231         float[] corners = {
232                 r.left, r.top, // 0
233                 r.right, r.top, // 1
234                 r.right, r.bottom,// 2
235                 r.left, r.bottom // 3
236         };
237         return corners;
238     }
239
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)
244             return;
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);
248         }
249     }
250
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]);
257         crop.close();
258         canvas.drawPath(crop, paint);
259         return crop;
260     }
261
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);
269
270     }
271
272     protected static float getNewHeightForWidthAspect(float width, float w, float h) {
273         return width * h / w;
274     }
275
276     protected static float getNewWidthForHeightAspect(float height, float w, float h) {
277         return height * w / h;
278     }
279
280     @Override
281     protected void onVisibilityChanged(View changedView, int visibility) {
282         super.onVisibilityChanged(changedView, visibility);
283         if (visibility == View.VISIBLE) {
284             mVisibilityGained = true;
285             syncLocalToMasterGeometry();
286             updateScale();
287             gainedVisibility();
288         } else {
289             if (mVisibilityGained == true && mHasDrawn == true) {
290                 lostVisibility();
291             }
292             mVisibilityGained = false;
293             mHasDrawn = false;
294         }
295     }
296
297     protected void gainedVisibility() {
298         // TODO: Override this stub.
299     }
300
301     protected void lostVisibility() {
302         // TODO: Override this stub.
303     }
304
305     @Override
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));
309     }
310
311     @Override
312     public boolean onTouchEvent(MotionEvent event) {
313         switch (event.getActionMasked()) {
314             case (MotionEvent.ACTION_DOWN):
315                 setActionDown(event.getX(), event.getY());
316                 break;
317             case (MotionEvent.ACTION_UP):
318                 setActionUp();
319                 saveAndSetPreset();
320                 break;
321             case (MotionEvent.ACTION_MOVE):
322                 setActionMove(event.getX(), event.getY());
323                 break;
324             default:
325                 setNoAction();
326         }
327         if (getPanelController() != null) {
328             getPanelController().onNewValue(getLocalValue());
329         }
330         invalidate();
331         return true;
332     }
333
334     protected int getLocalValue() {
335         return 0; // TODO: Override this
336     }
337
338     protected void setActionDown(float x, float y) {
339         mTouchCenterX = x;
340         mTouchCenterY = y;
341         mCurrentX = x;
342         mCurrentY = y;
343         mMode = MODES.DOWN;
344     }
345
346     protected void setActionMove(float x, float y) {
347         mCurrentX = x;
348         mCurrentY = y;
349         mMode = MODES.MOVE;
350     }
351
352     protected void setActionUp() {
353         mMode = MODES.UP;
354     }
355
356     protected void setNoAction() {
357         mMode = MODES.NONE;
358     }
359
360     @Override
361     public boolean showTitle() {
362         return false;
363     }
364
365     public String getName() {
366         return "Geometry";
367     }
368
369     protected void saveAndSetPreset() {
370         ImagePreset lastHistoryItem = getHistory().getLast();
371         if (lastHistoryItem != null && lastHistoryItem.historyName().equalsIgnoreCase(getName())) {
372             getImagePreset().setGeometry(mLocalGeometry);
373             resetImageCaches(this);
374         } else {
375             ImagePreset copy = new ImagePreset(getImagePreset());
376             copy.setGeometry(mLocalGeometry);
377             copy.setHistoryName(getName());
378             copy.setIsFx(false);
379             setImagePreset(copy, true);
380         }
381         invalidate();
382     }
383
384     public static RectF getUntranslatedStraightenCropBounds(RectF imageRect, float straightenAngle) {
385         float deg = straightenAngle;
386         if (deg < 0) {
387             deg = -deg;
388         }
389         double a = Math.toRadians(deg);
390         double sina = Math.sin(a);
391         double cosa = Math.cos(a);
392
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;
399
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);
404
405         return new RectF(left, top, right, bottom);
406     }
407
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);
415     }
416
417     protected void drawImageBitmap(Canvas canvas, Bitmap bitmap, Paint paint, Matrix m) {
418         canvas.save();
419         canvas.drawBitmap(bitmap, m, paint);
420         canvas.restore();
421     }
422
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);
429     }
430
431     protected RectF straightenBounds() {
432         RectF bounds = getUntranslatedStraightenCropBounds(getLocalPhotoBounds(),
433                 getLocalStraighten());
434         Matrix m = getGeoMatrix(bounds, true);
435         m.mapRect(bounds);
436         return bounds;
437     }
438
439     protected void drawStraighten(Canvas canvas, Paint paint) {
440         RectF bounds = straightenBounds();
441         canvas.save();
442         canvas.drawRect(bounds, paint);
443         canvas.restore();
444     }
445
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);
453         m.mapRect(bounds);
454         return bounds;
455     }
456
457     protected RectF cropBounds() {
458         RectF bounds = getLocalCropBounds();
459         Matrix m = getGeoMatrix(getLocalPhotoBounds(), true);
460         m.mapRect(bounds);
461         return bounds;
462     }
463
464     // Fails for non-90 degree
465     protected void drawCrop(Canvas canvas, Paint paint) {
466         RectF bounds = cropBounds();
467         canvas.save();
468         canvas.drawRect(bounds, paint);
469         canvas.restore();
470     }
471
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);
479         } else {
480             m.mapRect(crop);
481             Path path = new Path();
482             path.addRect(crop, Path.Direction.CCW);
483             canvas.drawPath(path, paint);
484         }
485     }
486
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());
497     }
498
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,
502                 getHeight() / 2);
503     }
504
505     protected static void drawShadows(Canvas canvas, Paint p, RectF innerBounds, RectF outerBounds,
506             float rotation, float centerX, float centerY) {
507         canvas.save();
508         canvas.rotate(rotation, centerX, centerY);
509
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,
520                 p);
521         canvas.drawRect(innerBounds.right, minY, maxX,
522                 innerBounds.bottom, p);
523         canvas.rotate(-rotation, centerX, centerY);
524         canvas.restore();
525     }
526
527     @Override
528     public void onDraw(Canvas canvas) {
529         if (getDirtyGeometryFlag()) {
530             syncLocalToMasterGeometry();
531             clearDirtyGeometryFlag();
532         }
533         requestFilteredImages();
534         Bitmap image = getMaster().getFiltersOnlyImage();
535         if (image == null) {
536             invalidate();
537             return;
538         }
539         mHasDrawn = true;
540         drawShape(canvas, image);
541     }
542
543     protected void drawShape(Canvas canvas, Bitmap image) {
544         // TODO: Override this stub.
545     }
546
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);
558
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);
564
565         m.preScale(scale, scale);
566         canvas.save();
567         canvas.drawBitmap(photo, m, p);
568         canvas.restore();
569
570         p.setColor(Color.WHITE);
571         p.setStyle(Style.STROKE);
572         p.setStrokeWidth(2);
573         canvas.drawPath(path, p);
574         return scaledCrop;
575     }
576
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;
585         }
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);
597
598         p.setARGB(255, 0, 0, 0);
599         canvas.save();
600         canvas.drawBitmap(photo, m1, p);
601         canvas.restore();
602
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);
608     }
609 }