OSDN Git Service

Fix colors in rotate and mirror
[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         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 = getMaster().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 Matrix getLocalGeoFlipMatrix(float width, float height) {
195         return mLocalGeometry.getFlipMatrix(width, height);
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 float[] getCornersFromRect(RectF r) {
221         // Order is:
222         // 0------->1
223         // ^        |
224         // |        v
225         // 3<-------2
226         float[] corners = {
227                 r.left, r.top, // 0
228                 r.right, r.top, // 1
229                 r.right, r.bottom,// 2
230                 r.left, r.bottom // 3
231         };
232         return corners;
233     }
234
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)
239             return;
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);
243         }
244     }
245
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]);
252         crop.close();
253         canvas.drawPath(crop, paint);
254         return crop;
255     }
256
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);
264
265     }
266
267     protected static float getNewHeightForWidthAspect(float width, float w, float h) {
268         return width * h / w;
269     }
270
271     protected static float getNewWidthForHeightAspect(float height, float w, float h) {
272         return height * w / h;
273     }
274
275     @Override
276     protected void onVisibilityChanged(View changedView, int visibility) {
277         super.onVisibilityChanged(changedView, visibility);
278         if (visibility == View.VISIBLE) {
279             mVisibilityGained = true;
280             syncLocalToMasterGeometry();
281             updateScale();
282             gainedVisibility();
283         } else {
284             if (mVisibilityGained == true && mHasDrawn == true) {
285                 lostVisibility();
286             }
287             mVisibilityGained = false;
288             mHasDrawn = false;
289         }
290     }
291
292     protected void gainedVisibility() {
293         // TODO: Override this stub.
294     }
295
296     protected void lostVisibility() {
297         // TODO: Override this stub.
298     }
299
300     @Override
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));
304     }
305
306     @Override
307     public boolean onTouchEvent(MotionEvent event) {
308         switch (event.getActionMasked()) {
309             case (MotionEvent.ACTION_DOWN):
310                 setActionDown(event.getX(), event.getY());
311                 break;
312             case (MotionEvent.ACTION_UP):
313                 setActionUp();
314                 saveAndSetPreset();
315                 break;
316             case (MotionEvent.ACTION_MOVE):
317                 setActionMove(event.getX(), event.getY());
318                 break;
319             default:
320                 setNoAction();
321         }
322         if (getPanelController() != null) {
323             getPanelController().onNewValue(getLocalValue());
324         }
325         invalidate();
326         return true;
327     }
328
329     protected int getLocalValue() {
330         return 0; // TODO: Override this
331     }
332
333     protected void setActionDown(float x, float y) {
334         mTouchCenterX = x;
335         mTouchCenterY = y;
336         mCurrentX = x;
337         mCurrentY = y;
338         mMode = MODES.DOWN;
339     }
340
341     protected void setActionMove(float x, float y) {
342         mCurrentX = x;
343         mCurrentY = y;
344         mMode = MODES.MOVE;
345     }
346
347     protected void setActionUp() {
348         mMode = MODES.UP;
349     }
350
351     protected void setNoAction() {
352         mMode = MODES.NONE;
353     }
354
355     @Override
356     public boolean showTitle() {
357         return false;
358     }
359
360     public String getName() {
361         return "Geometry";
362     }
363
364     public void saveAndSetPreset() {
365         ImagePreset lastHistoryItem = getHistory().getLast();
366         if (lastHistoryItem != null && lastHistoryItem.historyName().equalsIgnoreCase(getName())) {
367             getImagePreset().setGeometry(mLocalGeometry);
368             resetImageCaches(this);
369         } else {
370             if (mLocalGeometry.hasModifications()) {
371                 ImagePreset copy = new ImagePreset(getImagePreset());
372                 copy.setGeometry(mLocalGeometry);
373                 copy.setHistoryName(getName());
374                 copy.setIsFx(false);
375                 setImagePreset(copy, true);
376             }
377         }
378         invalidate();
379     }
380
381     public static RectF getUntranslatedStraightenCropBounds(RectF imageRect, float straightenAngle) {
382         float deg = straightenAngle;
383         if (deg < 0) {
384             deg = -deg;
385         }
386         double a = Math.toRadians(deg);
387         double sina = Math.sin(a);
388         double cosa = Math.cos(a);
389
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;
396
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);
401
402         return new RectF(left, top, right, bottom);
403     }
404
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());
411         }
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);
417     }
418
419     protected void drawImageBitmap(Canvas canvas, Bitmap bitmap, Paint paint, Matrix m) {
420         canvas.save();
421         canvas.drawBitmap(bitmap, m, paint);
422         canvas.restore();
423     }
424
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);
431     }
432
433     protected RectF straightenBounds() {
434         RectF bounds = getUntranslatedStraightenCropBounds(getLocalPhotoBounds(),
435                 getLocalStraighten());
436         Matrix m = getGeoMatrix(bounds, true);
437         m.mapRect(bounds);
438         return bounds;
439     }
440
441     protected void drawStraighten(Canvas canvas, Paint paint) {
442         RectF bounds = straightenBounds();
443         canvas.save();
444         canvas.drawRect(bounds, paint);
445         canvas.restore();
446     }
447
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,
455                 xoff, yoff, 0);
456         m.mapRect(bounds);
457         return bounds;
458     }
459
460     protected RectF cropBounds() {
461         RectF bounds = getLocalCropBounds();
462         Matrix m = getGeoMatrix(getLocalPhotoBounds(), true);
463         m.mapRect(bounds);
464         return bounds;
465     }
466
467     // Fails for non-90 degree
468     protected void drawCrop(Canvas canvas, Paint paint) {
469         RectF bounds = cropBounds();
470         canvas.save();
471         canvas.drawRect(bounds, paint);
472         canvas.restore();
473     }
474
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);
482         } else {
483             m.mapRect(crop);
484             Path path = new Path();
485             path.addRect(crop, Path.Direction.CCW);
486             canvas.drawPath(path, paint);
487         }
488     }
489
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());
500     }
501
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,
505                 getHeight() / 2);
506     }
507
508     protected static void drawShadows(Canvas canvas, Paint p, RectF innerBounds, RectF outerBounds,
509             float rotation, float centerX, float centerY) {
510         canvas.save();
511         canvas.rotate(rotation, centerX, centerY);
512
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,
523                 p);
524         canvas.drawRect(innerBounds.right, minY, maxX,
525                 innerBounds.bottom, p);
526         canvas.rotate(-rotation, centerX, centerY);
527         canvas.restore();
528     }
529
530     @Override
531     public void onDraw(Canvas canvas) {
532         if (getDirtyGeometryFlag()) {
533             syncLocalToMasterGeometry();
534             clearDirtyGeometryFlag();
535         }
536         requestFilteredImages();
537         Bitmap image = getMaster().getFiltersOnlyImage();
538         if (image == null) {
539             invalidate();
540             return;
541         }
542         mHasDrawn = true;
543         drawShape(canvas, image);
544     }
545
546     protected void drawShape(Canvas canvas, Bitmap image) {
547         // TODO: Override this stub.
548     }
549
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());
558         }
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
564         };
565         Matrix m = GeometryMetadata.buildCenteredPhotoMatrix(scaledPhoto, scaledCrop,
566                 getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter);
567
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);
573
574         m.preScale(scale, scale);
575         canvas.save();
576         canvas.drawBitmap(photo, m, p);
577         canvas.restore();
578
579         p.setColor(Color.WHITE);
580         p.setStyle(Style.STROKE);
581         p.setStrokeWidth(2);
582         canvas.drawPath(path, p);
583         return scaledCrop;
584     }
585
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());
595         }
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
601         };
602         Matrix m1 = GeometryMetadata.buildWanderingCropMatrix(scaledPhoto, scaledCrop,
603                 getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter);
604         float[] cropCenter = {
605                 scaledCrop.centerX(), scaledCrop.centerY()
606         };
607         m1.mapPoints(cropCenter);
608         GeometryMetadata.concatRecenterMatrix(m1, cropCenter, displayCenter);
609         m1.preRotate(getLocalStraighten(), scaledPhoto.centerX(), scaledPhoto.centerY());
610         m1.preScale(scale, scale);
611
612         p.setARGB(255, 0, 0, 0);
613         canvas.save();
614         canvas.drawBitmap(photo, m1, p);
615         canvas.restore();
616
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);
622     }
623 }