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.Canvas;
21import android.graphics.Matrix;
22import android.graphics.Paint;
23import android.graphics.Rect;
24import android.graphics.RectF;
25import android.util.Log;
26
27import com.android.gallery3d.filtershow.cache.BitmapCache;
28import com.android.gallery3d.filtershow.cache.ImageLoader;
29import com.android.gallery3d.filtershow.filters.FilterCropRepresentation;
30import com.android.gallery3d.filtershow.filters.FilterMirrorRepresentation;
31import com.android.gallery3d.filtershow.filters.FilterMirrorRepresentation.Mirror;
32import com.android.gallery3d.filtershow.filters.FilterRepresentation;
33import com.android.gallery3d.filtershow.filters.FilterRotateRepresentation;
34import com.android.gallery3d.filtershow.filters.FilterRotateRepresentation.Rotation;
35import com.android.gallery3d.filtershow.filters.FilterStraightenRepresentation;
36import com.android.gallery3d.filtershow.pipeline.ImagePreset;
37
38import java.util.Collection;
39import java.util.Iterator;
40
41public final class GeometryMathUtils {
42    private static final String TAG = "GeometryMathUtils";
43    public static final float SHOW_SCALE = .9f;
44
45    private GeometryMathUtils() {};
46
47    // Holder class for Geometry data.
48    public static final class GeometryHolder {
49        public Rotation rotation = FilterRotateRepresentation.getNil();
50        public float straighten = FilterStraightenRepresentation.getNil();
51        public RectF crop = FilterCropRepresentation.getNil();
52        public Mirror mirror = FilterMirrorRepresentation.getNil();
53
54        public void set(GeometryHolder h) {
55            rotation = h.rotation;
56            straighten = h.straighten;
57            crop.set(h.crop);
58            mirror = h.mirror;
59        }
60
61        public void wipe() {
62            rotation = FilterRotateRepresentation.getNil();
63            straighten = FilterStraightenRepresentation.getNil();
64            crop = FilterCropRepresentation.getNil();
65            mirror = FilterMirrorRepresentation.getNil();
66        }
67
68        public boolean isNil() {
69            return rotation == FilterRotateRepresentation.getNil() &&
70                    straighten == FilterStraightenRepresentation.getNil() &&
71                    crop.equals(FilterCropRepresentation.getNil()) &&
72                    mirror == FilterMirrorRepresentation.getNil();
73        }
74
75        @Override
76        public boolean equals(Object o) {
77            if (this == o) {
78                return true;
79            }
80            if (!(o instanceof GeometryHolder)) {
81                return false;
82            }
83            GeometryHolder h = (GeometryHolder) o;
84            return rotation == h.rotation && straighten == h.straighten &&
85                    ((crop == null && h.crop == null) || (crop != null && crop.equals(h.crop))) &&
86                    mirror == h.mirror;
87        }
88
89        @Override
90        public String toString() {
91            return getClass().getSimpleName() + "[" + "rotation:" + rotation.value()
92                    + ",straighten:" + straighten + ",crop:" + crop.toString()
93                    + ",mirror:" + mirror.value() + "]";
94        }
95    }
96
97    // Math operations for 2d vectors
98    public static float clamp(float i, float low, float high) {
99        return Math.max(Math.min(i, high), low);
100    }
101
102    public static float[] lineIntersect(float[] line1, float[] line2) {
103        float a0 = line1[0];
104        float a1 = line1[1];
105        float b0 = line1[2];
106        float b1 = line1[3];
107        float c0 = line2[0];
108        float c1 = line2[1];
109        float d0 = line2[2];
110        float d1 = line2[3];
111        float t0 = a0 - b0;
112        float t1 = a1 - b1;
113        float t2 = b0 - d0;
114        float t3 = d1 - b1;
115        float t4 = c0 - d0;
116        float t5 = c1 - d1;
117
118        float denom = t1 * t4 - t0 * t5;
119        if (denom == 0)
120            return null;
121        float u = (t3 * t4 + t5 * t2) / denom;
122        float[] intersect = {
123                b0 + u * t0, b1 + u * t1
124        };
125        return intersect;
126    }
127
128    public static float[] shortestVectorFromPointToLine(float[] point, float[] line) {
129        float x1 = line[0];
130        float x2 = line[2];
131        float y1 = line[1];
132        float y2 = line[3];
133        float xdelt = x2 - x1;
134        float ydelt = y2 - y1;
135        if (xdelt == 0 && ydelt == 0)
136            return null;
137        float u = ((point[0] - x1) * xdelt + (point[1] - y1) * ydelt)
138                / (xdelt * xdelt + ydelt * ydelt);
139        float[] ret = {
140                (x1 + u * (x2 - x1)), (y1 + u * (y2 - y1))
141        };
142        float[] vec = {
143                ret[0] - point[0], ret[1] - point[1]
144        };
145        return vec;
146    }
147
148    // A . B
149    public static float dotProduct(float[] a, float[] b) {
150        return a[0] * b[0] + a[1] * b[1];
151    }
152
153    public static float[] normalize(float[] a) {
154        float length = (float) Math.sqrt(a[0] * a[0] + a[1] * a[1]);
155        float[] b = {
156                a[0] / length, a[1] / length
157        };
158        return b;
159    }
160
161    // A onto B
162    public static float scalarProjection(float[] a, float[] b) {
163        float length = (float) Math.sqrt(b[0] * b[0] + b[1] * b[1]);
164        return dotProduct(a, b) / length;
165    }
166
167    public static float[] getVectorFromPoints(float[] point1, float[] point2) {
168        float[] p = {
169                point2[0] - point1[0], point2[1] - point1[1]
170        };
171        return p;
172    }
173
174    public static float[] getUnitVectorFromPoints(float[] point1, float[] point2) {
175        float[] p = {
176                point2[0] - point1[0], point2[1] - point1[1]
177        };
178        float length = (float) Math.sqrt(p[0] * p[0] + p[1] * p[1]);
179        p[0] = p[0] / length;
180        p[1] = p[1] / length;
181        return p;
182    }
183
184    public static void scaleRect(RectF r, float scale) {
185        r.set(r.left * scale, r.top * scale, r.right * scale, r.bottom * scale);
186    }
187
188    // A - B
189    public static float[] vectorSubtract(float[] a, float[] b) {
190        int len = a.length;
191        if (len != b.length)
192            return null;
193        float[] ret = new float[len];
194        for (int i = 0; i < len; i++) {
195            ret[i] = a[i] - b[i];
196        }
197        return ret;
198    }
199
200    public static float vectorLength(float[] a) {
201        return (float) Math.sqrt(a[0] * a[0] + a[1] * a[1]);
202    }
203
204    public static float scale(float oldWidth, float oldHeight, float newWidth, float newHeight) {
205        if (oldHeight == 0 || oldWidth == 0 || (oldWidth == newWidth && oldHeight == newHeight)) {
206            return 1;
207        }
208        return Math.min(newWidth / oldWidth, newHeight / oldHeight);
209    }
210
211    public static Rect roundNearest(RectF r) {
212        Rect q = new Rect(Math.round(r.left), Math.round(r.top), Math.round(r.right),
213                Math.round(r.bottom));
214        return q;
215    }
216
217    private static void concatMirrorMatrix(Matrix m, GeometryHolder holder) {
218        Mirror type = holder.mirror;
219        if (type == Mirror.HORIZONTAL) {
220            if (holder.rotation.value() == 90
221                    || holder.rotation.value() == 270) {
222                type = Mirror.VERTICAL;
223            }
224        } else if (type == Mirror.VERTICAL) {
225            if (holder.rotation.value() == 90
226                    || holder.rotation.value() == 270) {
227                type = Mirror.HORIZONTAL;
228            }
229        }
230        if (type == Mirror.HORIZONTAL) {
231            m.postScale(-1, 1);
232        } else if (type == Mirror.VERTICAL) {
233            m.postScale(1, -1);
234        } else if (type == Mirror.BOTH) {
235            m.postScale(1, -1);
236            m.postScale(-1, 1);
237        }
238    }
239
240    private static int getRotationForOrientation(int orientation) {
241        switch (orientation) {
242            case ImageLoader.ORI_ROTATE_90:
243                return 90;
244            case ImageLoader.ORI_ROTATE_180:
245                return 180;
246            case ImageLoader.ORI_ROTATE_270:
247                return 270;
248            default:
249                return 0;
250        }
251    }
252
253    public static GeometryHolder unpackGeometry(Collection<FilterRepresentation> geometry) {
254        GeometryHolder holder = new GeometryHolder();
255        unpackGeometry(holder, geometry);
256        return holder;
257    }
258
259    public static void unpackGeometry(GeometryHolder out,
260            Collection<FilterRepresentation> geometry) {
261        out.wipe();
262        // Get geometry data from filters
263        for (FilterRepresentation r : geometry) {
264            if (r.isNil()) {
265                continue;
266            }
267            if (r.getSerializationName() == FilterRotateRepresentation.SERIALIZATION_NAME) {
268                out.rotation = ((FilterRotateRepresentation) r).getRotation();
269            } else if (r.getSerializationName() ==
270                    FilterStraightenRepresentation.SERIALIZATION_NAME) {
271                out.straighten = ((FilterStraightenRepresentation) r).getStraighten();
272            } else if (r.getSerializationName() == FilterCropRepresentation.SERIALIZATION_NAME) {
273                ((FilterCropRepresentation) r).getCrop(out.crop);
274            } else if (r.getSerializationName() == FilterMirrorRepresentation.SERIALIZATION_NAME) {
275                out.mirror = ((FilterMirrorRepresentation) r).getMirror();
276            }
277        }
278    }
279
280    public static void replaceInstances(Collection<FilterRepresentation> geometry,
281            FilterRepresentation rep) {
282        Iterator<FilterRepresentation> iter = geometry.iterator();
283        while (iter.hasNext()) {
284            FilterRepresentation r = iter.next();
285            if (ImagePreset.sameSerializationName(rep, r)) {
286                iter.remove();
287            }
288        }
289        if (!rep.isNil()) {
290            geometry.add(rep);
291        }
292    }
293
294    public static void initializeHolder(GeometryHolder outHolder,
295            FilterRepresentation currentLocal) {
296        Collection<FilterRepresentation> geometry = MasterImage.getImage().getPreset()
297                .getGeometryFilters();
298        replaceInstances(geometry, currentLocal);
299        unpackGeometry(outHolder, geometry);
300    }
301
302    public static Rect finalGeometryRect(int width, int height,
303                                         Collection<FilterRepresentation> geometry) {
304        GeometryHolder holder = unpackGeometry(geometry);
305        RectF crop = getTrueCropRect(holder, width, height);
306        Rect frame = new Rect();
307        crop.roundOut(frame);
308        return frame;
309    }
310
311    private static Bitmap applyFullGeometryMatrix(Bitmap image, GeometryHolder holder) {
312        int width = image.getWidth();
313        int height = image.getHeight();
314        RectF crop = getTrueCropRect(holder, width, height);
315        Rect frame = new Rect();
316        crop.roundOut(frame);
317        Matrix m = getCropSelectionToScreenMatrix(null, holder, width, height, frame.width(),
318                frame.height());
319        BitmapCache bitmapCache = MasterImage.getImage().getBitmapCache();
320        Bitmap temp = bitmapCache.getBitmap(frame.width(),
321                frame.height(), BitmapCache.UTIL_GEOMETRY);
322        Canvas canvas = new Canvas(temp);
323        Paint paint = new Paint();
324        paint.setAntiAlias(true);
325        paint.setFilterBitmap(true);
326        paint.setDither(true);
327        canvas.drawBitmap(image, m, paint);
328        return temp;
329    }
330
331    public static Matrix getImageToScreenMatrix(Collection<FilterRepresentation> geometry,
332            boolean reflectRotation, Rect bmapDimens, float viewWidth, float viewHeight) {
333        GeometryHolder h = unpackGeometry(geometry);
334        return GeometryMathUtils.getOriginalToScreen(h, reflectRotation, bmapDimens.width(),
335                bmapDimens.height(), viewWidth, viewHeight);
336    }
337
338    public static Matrix getPartialToScreenMatrix(Collection<FilterRepresentation> geometry,
339                                                  Rect originalBounds, float w, float h,
340                                                  float pw, float ph) {
341        GeometryHolder holder = unpackGeometry(geometry);
342        RectF rCrop = new RectF(0, 0, originalBounds.width(), originalBounds.height());
343        float angle = holder.straighten;
344        int rotation = holder.rotation.value();
345
346        ImageStraighten.getUntranslatedStraightenCropBounds(rCrop, angle);
347        float dx = (w - pw) / 2f;
348        float dy = (h - ph) / 2f;
349        Matrix compensation = new Matrix();
350        compensation.postTranslate(dx, dy);
351        float cScale = originalBounds.width() / rCrop.width();
352        if (rCrop.width() < rCrop.height()) {
353            cScale = originalBounds.height() / rCrop.height();
354        }
355        float scale = w / pw;
356        if (w < h) {
357            scale = h / ph;
358        }
359        scale = scale * cScale;
360        float cx = w / 2f;
361        float cy = h / 2f;
362
363        compensation.postScale(scale, scale, cx, cy);
364        compensation.postRotate(angle, cx, cy);
365        compensation.postRotate(rotation, cx, cy);
366        compensation.postTranslate(-cx, -cy);
367        concatMirrorMatrix(compensation, holder);
368        compensation.postTranslate(cx, cy);
369        return compensation;
370    }
371
372    public static Matrix getOriginalToScreen(GeometryHolder holder, boolean rotate,
373            float originalWidth,
374            float originalHeight, float viewWidth, float viewHeight) {
375        int orientation = MasterImage.getImage().getZoomOrientation();
376        int rotation = getRotationForOrientation(orientation);
377        Rotation prev = holder.rotation;
378        rotation = (rotation + prev.value()) % 360;
379        holder.rotation = Rotation.fromValue(rotation);
380        Matrix m = getCropSelectionToScreenMatrix(null, holder, (int) originalWidth,
381                (int) originalHeight, (int) viewWidth, (int) viewHeight);
382        holder.rotation = prev;
383        return m;
384    }
385
386    public static Bitmap applyGeometryRepresentations(Collection<FilterRepresentation> res,
387            Bitmap image) {
388        GeometryHolder holder = unpackGeometry(res);
389        Bitmap bmap = image;
390        // If there are geometry changes, apply them to the image
391        if (!holder.isNil()) {
392            bmap = applyFullGeometryMatrix(bmap, holder);
393            if (bmap != image) {
394                BitmapCache cache = MasterImage.getImage().getBitmapCache();
395                cache.cache(image);
396            }
397        }
398        return bmap;
399    }
400
401    public static RectF drawTransformedCropped(GeometryHolder holder, Canvas canvas,
402            Bitmap photo, int viewWidth, int viewHeight) {
403        if (photo == null) {
404            return null;
405        }
406        RectF crop = new RectF();
407        Matrix m = getCropSelectionToScreenMatrix(crop, holder, photo.getWidth(), photo.getHeight(),
408                viewWidth, viewHeight);
409        canvas.save();
410        canvas.clipRect(crop);
411        Paint p = new Paint();
412        p.setAntiAlias(true);
413        canvas.drawBitmap(photo, m, p);
414        canvas.restore();
415        return crop;
416    }
417
418    public static boolean needsDimensionSwap(Rotation rotation) {
419        switch (rotation) {
420            case NINETY:
421            case TWO_SEVENTY:
422                return true;
423            default:
424                return false;
425        }
426    }
427
428    // Gives matrix for rotated, straightened, mirrored bitmap centered at 0,0.
429    private static Matrix getFullGeometryMatrix(GeometryHolder holder, int bitmapWidth,
430            int bitmapHeight) {
431        float centerX = bitmapWidth / 2f;
432        float centerY = bitmapHeight / 2f;
433        Matrix m = new Matrix();
434        m.setTranslate(-centerX, -centerY);
435        m.postRotate(holder.straighten + holder.rotation.value());
436        concatMirrorMatrix(m, holder);
437        return m;
438    }
439
440    public static Matrix getFullGeometryToScreenMatrix(GeometryHolder holder, int bitmapWidth,
441            int bitmapHeight, int viewWidth, int viewHeight) {
442        int bh = bitmapHeight;
443        int bw = bitmapWidth;
444        if (GeometryMathUtils.needsDimensionSwap(holder.rotation)) {
445            bh = bitmapWidth;
446            bw = bitmapHeight;
447        }
448        float scale = GeometryMathUtils.scale(bw, bh, viewWidth, viewHeight);
449        scale *= SHOW_SCALE;
450        float s = Math.min(viewWidth / (float) bitmapWidth, viewHeight / (float) bitmapHeight);
451        Matrix m = getFullGeometryMatrix(holder, bitmapWidth, bitmapHeight);
452        m.postScale(scale, scale);
453        m.postTranslate(viewWidth / 2f, viewHeight / 2f);
454        return m;
455    }
456
457    public static RectF getTrueCropRect(GeometryHolder holder, int bitmapWidth, int bitmapHeight) {
458        RectF r = new RectF(holder.crop);
459        FilterCropRepresentation.findScaledCrop(r, bitmapWidth, bitmapHeight);
460        float s = holder.straighten;
461        holder.straighten = 0;
462        Matrix m1 = getFullGeometryMatrix(holder, bitmapWidth, bitmapHeight);
463        holder.straighten = s;
464        m1.mapRect(r);
465        return r;
466    }
467
468    public static Matrix getCropSelectionToScreenMatrix(RectF outCrop, GeometryHolder holder,
469            int bitmapWidth, int bitmapHeight, int viewWidth, int viewHeight) {
470        Matrix m = getFullGeometryMatrix(holder, bitmapWidth, bitmapHeight);
471        RectF crop = getTrueCropRect(holder, bitmapWidth, bitmapHeight);
472        float scale = GeometryMathUtils.scale(crop.width(), crop.height(), viewWidth, viewHeight);
473        m.postScale(scale, scale);
474        GeometryMathUtils.scaleRect(crop, scale);
475        m.postTranslate(viewWidth / 2f - crop.centerX(), viewHeight / 2f - crop.centerY());
476        if (outCrop != null) {
477            crop.offset(viewWidth / 2f - crop.centerX(), viewHeight / 2f - crop.centerY());
478            outCrop.set(crop);
479        }
480        return m;
481    }
482
483    public static Matrix getCropSelectionToScreenMatrix(RectF outCrop,
484            Collection<FilterRepresentation> res, int bitmapWidth, int bitmapHeight, int viewWidth,
485            int viewHeight) {
486        GeometryHolder holder = unpackGeometry(res);
487        return getCropSelectionToScreenMatrix(outCrop, holder, bitmapWidth, bitmapHeight,
488                viewWidth, viewHeight);
489    }
490}
491