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
17package com.android.gallery3d.filtershow.imageshow;
18
19import android.graphics.Bitmap;
20import android.graphics.Matrix;
21import android.graphics.Rect;
22import android.graphics.RectF;
23
24import com.android.gallery3d.filtershow.cache.ImageLoader;
25import com.android.gallery3d.filtershow.filters.ImageFilterGeometry;
26
27public class GeometryMetadata {
28    // Applied in order: rotate, crop, scale.
29    // Do not scale saved image (presumably?).
30    private static final ImageFilterGeometry mImageFilter = new ImageFilterGeometry();
31    private static final String LOGTAG = "GeometryMetadata";
32    private float mScaleFactor = 1.0f;
33    private float mRotation = 0;
34    private float mStraightenRotation = 0;
35    private final RectF mCropBounds = new RectF();
36    private final RectF mPhotoBounds = new RectF();
37    private FLIP mFlip = FLIP.NONE;
38
39    private RectF mBounds = new RectF();
40
41    public enum FLIP {
42        NONE, VERTICAL, HORIZONTAL, BOTH
43    }
44
45    public GeometryMetadata() {
46    }
47
48    public GeometryMetadata(GeometryMetadata g) {
49        set(g);
50    }
51
52    public boolean hasModifications() {
53        if (mScaleFactor != 1.0f) {
54            return true;
55        }
56        if (mRotation != 0) {
57            return true;
58        }
59        if (mStraightenRotation != 0) {
60            return true;
61        }
62        Rect cropBounds = GeometryMath.roundNearest(mCropBounds);
63        Rect photoBounds = GeometryMath.roundNearest(mPhotoBounds);
64        if (!cropBounds.equals(photoBounds)) {
65            return true;
66        }
67        if (!mFlip.equals(FLIP.NONE)) {
68            return true;
69        }
70        return false;
71    }
72
73    public Bitmap apply(Bitmap original, float scaleFactor, boolean highQuality) {
74        if (!hasModifications()) {
75            return original;
76        }
77        mImageFilter.setGeometryMetadata(this);
78        Bitmap m = mImageFilter.apply(original, scaleFactor, highQuality);
79        return m;
80    }
81
82    public void set(GeometryMetadata g) {
83        mScaleFactor = g.mScaleFactor;
84        mRotation = g.mRotation;
85        mStraightenRotation = g.mStraightenRotation;
86        mCropBounds.set(g.mCropBounds);
87        mPhotoBounds.set(g.mPhotoBounds);
88        mFlip = g.mFlip;
89        mBounds = g.mBounds;
90    }
91
92    public float getScaleFactor() {
93        return mScaleFactor;
94    }
95
96    public float getRotation() {
97        return mRotation;
98    }
99
100    public float getStraightenRotation() {
101        return mStraightenRotation;
102    }
103
104    public RectF getPreviewCropBounds() {
105        return new RectF(mCropBounds);
106    }
107
108    public RectF getCropBounds(Bitmap bitmap) {
109        float scale = 1.0f;
110        scale = GeometryMath.scale(mPhotoBounds.width(), mPhotoBounds.height(), bitmap.getWidth(),
111                bitmap.getHeight());
112        return new RectF(mCropBounds.left * scale, mCropBounds.top * scale,
113                mCropBounds.right * scale, mCropBounds.bottom * scale);
114    }
115
116    public FLIP getFlipType() {
117        return mFlip;
118    }
119
120    public RectF getPhotoBounds() {
121        return new RectF(mPhotoBounds);
122    }
123
124    public void setScaleFactor(float scale) {
125        mScaleFactor = scale;
126    }
127
128    public void setFlipType(FLIP flip) {
129        mFlip = flip;
130    }
131
132    public void setRotation(float rotation) {
133        mRotation = rotation;
134    }
135
136    public void setStraightenRotation(float straighten) {
137        mStraightenRotation = straighten;
138    }
139
140    public void setCropBounds(RectF newCropBounds) {
141        mCropBounds.set(newCropBounds);
142    }
143
144    public void setPhotoBounds(RectF newPhotoBounds) {
145        mPhotoBounds.set(newPhotoBounds);
146    }
147
148    public boolean cropFitsInPhoto(RectF cropBounds) {
149        return mPhotoBounds.contains(cropBounds);
150    }
151
152    @Override
153    public boolean equals(Object o) {
154        if (this == o)
155            return true;
156        if (o == null || getClass() != o.getClass())
157            return false;
158
159        GeometryMetadata d = (GeometryMetadata) o;
160        return (mScaleFactor == d.mScaleFactor &&
161                mRotation == d.mRotation &&
162                mStraightenRotation == d.mStraightenRotation &&
163                mFlip == d.mFlip &&
164                mCropBounds.equals(d.mCropBounds) && mPhotoBounds.equals(d.mPhotoBounds));
165    }
166
167    @Override
168    public int hashCode() {
169        int result = 23;
170        result = 31 * result + Float.floatToIntBits(mRotation);
171        result = 31 * result + Float.floatToIntBits(mStraightenRotation);
172        result = 31 * result + Float.floatToIntBits(mScaleFactor);
173        result = 31 * result + mFlip.hashCode();
174        result = 31 * result + mCropBounds.hashCode();
175        result = 31 * result + mPhotoBounds.hashCode();
176        return result;
177    }
178
179    @Override
180    public String toString() {
181        return getClass().getName() + "[" + "scale=" + mScaleFactor
182                + ",rotation=" + mRotation + ",flip=" + mFlip + ",straighten="
183                + mStraightenRotation + ",cropRect=" + mCropBounds.toShortString()
184                + ",photoRect=" + mPhotoBounds.toShortString() + "]";
185    }
186
187    // TODO: refactor away
188    protected static Matrix getHorizontalMatrix(float width) {
189        Matrix flipHorizontalMatrix = new Matrix();
190        flipHorizontalMatrix.setScale(-1, 1);
191        flipHorizontalMatrix.postTranslate(width, 0);
192        return flipHorizontalMatrix;
193    }
194
195    protected static void concatHorizontalMatrix(Matrix m, float width) {
196        m.postScale(-1, 1);
197        m.postTranslate(width, 0);
198    }
199
200    // TODO: refactor away
201    protected static Matrix getVerticalMatrix(float height) {
202        Matrix flipVerticalMatrix = new Matrix();
203        flipVerticalMatrix.setScale(1, -1);
204        flipVerticalMatrix.postTranslate(0, height);
205        return flipVerticalMatrix;
206    }
207
208    protected static void concatVerticalMatrix(Matrix m, float height) {
209        m.postScale(1, -1);
210        m.postTranslate(0, height);
211    }
212
213    // TODO: refactor away
214    public static Matrix getFlipMatrix(float width, float height, FLIP type) {
215        if (type == FLIP.HORIZONTAL) {
216            return getHorizontalMatrix(width);
217        } else if (type == FLIP.VERTICAL) {
218            return getVerticalMatrix(height);
219        } else if (type == FLIP.BOTH) {
220            Matrix flipper = getVerticalMatrix(height);
221            flipper.postConcat(getHorizontalMatrix(width));
222            return flipper;
223        } else {
224            Matrix m = new Matrix();
225            m.reset(); // identity
226            return m;
227        }
228    }
229
230    public static void concatMirrorMatrix(Matrix m, float width, float height, FLIP type) {
231        if (type == FLIP.HORIZONTAL) {
232            concatHorizontalMatrix(m, width);
233        } else if (type == FLIP.VERTICAL) {
234            concatVerticalMatrix(m, height);
235        } else if (type == FLIP.BOTH) {
236            concatVerticalMatrix(m, height);
237            concatHorizontalMatrix(m, width);
238        }
239    }
240
241    public Matrix getMatrixOriginalOrientation(int orientation, float originalWidth,
242            float originalHeight) {
243        Matrix imageRotation = new Matrix();
244        switch (orientation) {
245            case ImageLoader.ORI_ROTATE_90: {
246                imageRotation.setRotate(90, originalWidth / 2f, originalHeight / 2f);
247                imageRotation.postTranslate(-(originalWidth - originalHeight) / 2f,
248                        -(originalHeight - originalWidth) / 2f);
249                break;
250            }
251            case ImageLoader.ORI_ROTATE_180: {
252                imageRotation.setRotate(180, originalWidth / 2f, originalHeight / 2f);
253                break;
254            }
255            case ImageLoader.ORI_ROTATE_270: {
256                imageRotation.setRotate(270, originalWidth / 2f, originalHeight / 2f);
257                imageRotation.postTranslate(-(originalWidth - originalHeight) / 2f,
258                        -(originalHeight - originalWidth) / 2f);
259                break;
260            }
261            case ImageLoader.ORI_FLIP_HOR: {
262                imageRotation.preScale(-1, 1);
263                break;
264            }
265            case ImageLoader.ORI_FLIP_VERT: {
266                imageRotation.preScale(1, -1);
267                break;
268            }
269            case ImageLoader.ORI_TRANSPOSE: {
270                imageRotation.setRotate(90, originalWidth / 2f, originalHeight / 2f);
271                imageRotation.postTranslate(-(originalWidth - originalHeight) / 2f,
272                        -(originalHeight - originalWidth) / 2f);
273                imageRotation.preScale(1, -1);
274                break;
275            }
276            case ImageLoader.ORI_TRANSVERSE: {
277                imageRotation.setRotate(270, originalWidth / 2f, originalHeight / 2f);
278                imageRotation.postTranslate(-(originalWidth - originalHeight) / 2f,
279                        -(originalHeight - originalWidth) / 2f);
280                imageRotation.preScale(1, -1);
281                break;
282            }
283        }
284        return imageRotation;
285    }
286
287    public Matrix getOriginalToScreen(boolean rotate, float originalWidth, float originalHeight,
288            float viewWidth, float viewHeight) {
289        RectF photoBounds = getPhotoBounds();
290        RectF cropBounds = getPreviewCropBounds();
291        float imageWidth = cropBounds.width();
292        float imageHeight = cropBounds.height();
293
294        int orientation = ImageLoader.getZoomOrientation();
295        Matrix imageRotation = getMatrixOriginalOrientation(orientation, originalWidth,
296                originalHeight);
297        if (orientation == ImageLoader.ORI_ROTATE_90 ||
298                orientation == ImageLoader.ORI_ROTATE_270 ||
299                orientation == ImageLoader.ORI_TRANSPOSE ||
300                orientation == ImageLoader.ORI_TRANSVERSE) {
301            float tmp = originalWidth;
302            originalWidth = originalHeight;
303            originalHeight = tmp;
304        }
305
306        float preScale = GeometryMath.scale(originalWidth, originalHeight,
307                photoBounds.width(), photoBounds.height());
308        float scale = GeometryMath.scale(imageWidth, imageHeight, viewWidth, viewHeight);
309        // checks if local rotation is an odd multiple of 90.
310        if (((int) (getRotation() / 90)) % 2 != 0) {
311            scale = GeometryMath.scale(imageWidth, imageHeight, viewHeight, viewWidth);
312        }
313        // put in screen coordinates
314        RectF scaledCrop = GeometryMath.scaleRect(cropBounds, scale);
315        RectF scaledPhoto = GeometryMath.scaleRect(photoBounds, scale);
316        float[] displayCenter = {
317                viewWidth / 2f, viewHeight / 2f
318        };
319        Matrix m1 = GeometryMetadata.buildWanderingCropMatrix(scaledPhoto, scaledCrop,
320                getRotation(), getStraightenRotation(), getFlipType(), displayCenter);
321        float[] cropCenter = {
322                scaledCrop.centerX(), scaledCrop.centerY()
323        };
324        m1.mapPoints(cropCenter);
325        GeometryMetadata.concatRecenterMatrix(m1, cropCenter, displayCenter);
326        m1.preRotate(getStraightenRotation(), scaledPhoto.centerX(), scaledPhoto.centerY());
327        m1.preScale(scale, scale);
328        m1.preScale(preScale, preScale);
329        m1.preConcat(imageRotation);
330
331        return m1;
332    }
333
334    // TODO: refactor away
335    public Matrix getFlipMatrix(float width, float height) {
336        FLIP type = getFlipType();
337        return getFlipMatrix(width, height, type);
338    }
339
340    public boolean hasSwitchedWidthHeight() {
341        return (((int) (mRotation / 90)) % 2) != 0;
342    }
343
344    // TODO: refactor away
345    public Matrix buildGeometryMatrix(float width, float height, float scaling, float dx, float dy,
346            float rotation) {
347        float dx0 = width / 2;
348        float dy0 = height / 2;
349        Matrix m = getFlipMatrix(width, height);
350        m.postTranslate(-dx0, -dy0);
351        m.postRotate(rotation);
352        m.postScale(scaling, scaling);
353        m.postTranslate(dx, dy);
354        return m;
355    }
356
357    // TODO: refactor away
358    public Matrix buildGeometryMatrix(float width, float height, float scaling, float dx, float dy,
359            boolean onlyRotate) {
360        float rot = mRotation;
361        if (!onlyRotate) {
362            rot += mStraightenRotation;
363        }
364        return buildGeometryMatrix(width, height, scaling, dx, dy, rot);
365    }
366
367    // TODO: refactor away
368    public Matrix buildGeometryUIMatrix(float scaling, float dx, float dy) {
369        float w = mPhotoBounds.width();
370        float h = mPhotoBounds.height();
371        return buildGeometryMatrix(w, h, scaling, dx, dy, false);
372    }
373
374    public static Matrix buildPhotoMatrix(RectF photo, RectF crop, float rotation,
375            float straighten, FLIP type) {
376        Matrix m = new Matrix();
377        m.setRotate(straighten, photo.centerX(), photo.centerY());
378        concatMirrorMatrix(m, photo.right, photo.bottom, type);
379        m.postRotate(rotation, crop.centerX(), crop.centerY());
380
381        return m;
382    }
383
384    public static Matrix buildCropMatrix(RectF crop, float rotation) {
385        Matrix m = new Matrix();
386        m.setRotate(rotation, crop.centerX(), crop.centerY());
387        return m;
388    }
389
390    public static void concatRecenterMatrix(Matrix m, float[] currentCenter, float[] newCenter) {
391        m.postTranslate(newCenter[0] - currentCenter[0], newCenter[1] - currentCenter[1]);
392    }
393
394    /**
395     * Builds a matrix to transform a bitmap of width bmWidth and height
396     * bmHeight so that the region of the bitmap being cropped to is oriented
397     * and centered at displayCenter.
398     *
399     * @param bmWidth
400     * @param bmHeight
401     * @param displayCenter
402     * @return
403     */
404    public Matrix buildTotalXform(float bmWidth, float bmHeight, float[] displayCenter) {
405        RectF rp = getPhotoBounds();
406        RectF rc = getPreviewCropBounds();
407
408        float scale = GeometryMath.scale(rp.width(), rp.height(), bmWidth, bmHeight);
409        RectF scaledCrop = GeometryMath.scaleRect(rc, scale);
410        RectF scaledPhoto = GeometryMath.scaleRect(rp, scale);
411
412        Matrix m1 = GeometryMetadata.buildWanderingCropMatrix(scaledPhoto, scaledCrop,
413                getRotation(), getStraightenRotation(),
414                getFlipType(), displayCenter);
415        float[] cropCenter = {
416                scaledCrop.centerX(), scaledCrop.centerY()
417        };
418        m1.mapPoints(cropCenter);
419
420        GeometryMetadata.concatRecenterMatrix(m1, cropCenter, displayCenter);
421        m1.preRotate(getStraightenRotation(), scaledPhoto.centerX(),
422                scaledPhoto.centerY());
423        return m1;
424    }
425
426    /**
427     * Builds a matrix that rotates photo rect about it's center by the
428     * straighten angle, mirrors it about the crop center, and rotates it about
429     * the crop center by the rotation angle, and re-centers the photo rect.
430     *
431     * @param photo
432     * @param crop
433     * @param rotation
434     * @param straighten
435     * @param type
436     * @param newCenter
437     * @return
438     */
439    public static Matrix buildCenteredPhotoMatrix(RectF photo, RectF crop, float rotation,
440            float straighten, FLIP type, float[] newCenter) {
441        Matrix m = buildPhotoMatrix(photo, crop, rotation, straighten, type);
442        float[] center = {
443                photo.centerX(), photo.centerY()
444        };
445        m.mapPoints(center);
446        concatRecenterMatrix(m, center, newCenter);
447        return m;
448    }
449
450    /**
451     * Builds a matrix that rotates a crop rect about it's center by rotation
452     * angle, then re-centers the crop rect.
453     *
454     * @param crop
455     * @param rotation
456     * @param newCenter
457     * @return
458     */
459    public static Matrix buildCenteredCropMatrix(RectF crop, float rotation, float[] newCenter) {
460        Matrix m = buildCropMatrix(crop, rotation);
461        float[] center = {
462                crop.centerX(), crop.centerY()
463        };
464        m.mapPoints(center);
465        concatRecenterMatrix(m, center, newCenter);
466        return m;
467    }
468
469    /**
470     * Builds a matrix that transforms the crop rect to its view coordinates
471     * inside the photo rect.
472     *
473     * @param photo
474     * @param crop
475     * @param rotation
476     * @param straighten
477     * @param type
478     * @param newCenter
479     * @return
480     */
481    public static Matrix buildWanderingCropMatrix(RectF photo, RectF crop, float rotation,
482            float straighten, FLIP type, float[] newCenter) {
483        Matrix m = buildCenteredPhotoMatrix(photo, crop, rotation, straighten, type, newCenter);
484        m.preRotate(-straighten, photo.centerX(), photo.centerY());
485        return m;
486    }
487}
488