GeometryMathUtils.java revision ec1f915006e5ec2db8be633b5d3d73e568951da4
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(), frame.height());
321        Canvas canvas = new Canvas(temp);
322        Paint paint = new Paint();
323        paint.setAntiAlias(true);
324        paint.setFilterBitmap(true);
325        paint.setDither(true);
326        canvas.drawBitmap(image, m, paint);
327        return temp;
328    }
329
330    public static Matrix getImageToScreenMatrix(Collection<FilterRepresentation> geometry,
331            boolean reflectRotation, Rect bmapDimens, float viewWidth, float viewHeight) {
332        GeometryHolder h = unpackGeometry(geometry);
333        return GeometryMathUtils.getOriginalToScreen(h, reflectRotation, bmapDimens.width(),
334                bmapDimens.height(), viewWidth, viewHeight);
335    }
336
337    public static Matrix getPartialToScreenMatrix(Collection<FilterRepresentation> geometry,
338                                                  Rect originalBounds, float w, float h,
339                                                  float pw, float ph) {
340        GeometryHolder holder = unpackGeometry(geometry);
341        RectF rCrop = new RectF(0, 0, originalBounds.width(), originalBounds.height());
342        float angle = holder.straighten;
343        int rotation = holder.rotation.value();
344
345        ImageStraighten.getUntranslatedStraightenCropBounds(rCrop, angle);
346        float dx = (w - pw) / 2f;
347        float dy = (h - ph) / 2f;
348        Matrix compensation = new Matrix();
349        compensation.postTranslate(dx, dy);
350        float cScale = originalBounds.width() / rCrop.width();
351        if (rCrop.width() < rCrop.height()) {
352            cScale = originalBounds.height() / rCrop.height();
353        }
354        float scale = w / pw;
355        if (w < h) {
356            scale = h / ph;
357        }
358        scale = scale * cScale;
359        float cx = w / 2f;
360        float cy = h / 2f;
361
362        compensation.postScale(scale, scale, cx, cy);
363        compensation.postRotate(angle, cx, cy);
364        compensation.postRotate(rotation, cx, cy);
365        compensation.postTranslate(-cx, -cy);
366        concatMirrorMatrix(compensation, holder);
367        compensation.postTranslate(cx, cy);
368        return compensation;
369    }
370
371    public static Matrix getOriginalToScreen(GeometryHolder holder, boolean rotate,
372            float originalWidth,
373            float originalHeight, float viewWidth, float viewHeight) {
374        int orientation = MasterImage.getImage().getZoomOrientation();
375        int rotation = getRotationForOrientation(orientation);
376        Rotation prev = holder.rotation;
377        rotation = (rotation + prev.value()) % 360;
378        holder.rotation = Rotation.fromValue(rotation);
379        Matrix m = getCropSelectionToScreenMatrix(null, holder, (int) originalWidth,
380                (int) originalHeight, (int) viewWidth, (int) viewHeight);
381        holder.rotation = prev;
382        return m;
383    }
384
385    public static Bitmap applyGeometryRepresentations(Collection<FilterRepresentation> res,
386            Bitmap image) {
387        GeometryHolder holder = unpackGeometry(res);
388        Bitmap bmap = image;
389        // If there are geometry changes, apply them to the image
390        if (!holder.isNil()) {
391            bmap = applyFullGeometryMatrix(bmap, holder);
392            if (bmap != image) {
393                BitmapCache cache = MasterImage.getImage().getBitmapCache();
394                cache.cache(image);
395            }
396        }
397        return bmap;
398    }
399
400    public static RectF drawTransformedCropped(GeometryHolder holder, Canvas canvas,
401            Bitmap photo, int viewWidth, int viewHeight) {
402        if (photo == null) {
403            return null;
404        }
405        RectF crop = new RectF();
406        Matrix m = getCropSelectionToScreenMatrix(crop, holder, photo.getWidth(), photo.getHeight(),
407                viewWidth, viewHeight);
408        canvas.save();
409        canvas.clipRect(crop);
410        Paint p = new Paint();
411        p.setAntiAlias(true);
412        canvas.drawBitmap(photo, m, p);
413        canvas.restore();
414        return crop;
415    }
416
417    public static boolean needsDimensionSwap(Rotation rotation) {
418        switch (rotation) {
419            case NINETY:
420            case TWO_SEVENTY:
421                return true;
422            default:
423                return false;
424        }
425    }
426
427    // Gives matrix for rotated, straightened, mirrored bitmap centered at 0,0.
428    private static Matrix getFullGeometryMatrix(GeometryHolder holder, int bitmapWidth,
429            int bitmapHeight) {
430        float centerX = bitmapWidth / 2f;
431        float centerY = bitmapHeight / 2f;
432        Matrix m = new Matrix();
433        m.setTranslate(-centerX, -centerY);
434        m.postRotate(holder.straighten + holder.rotation.value());
435        concatMirrorMatrix(m, holder);
436        return m;
437    }
438
439    public static Matrix getFullGeometryToScreenMatrix(GeometryHolder holder, int bitmapWidth,
440            int bitmapHeight, int viewWidth, int viewHeight) {
441        int bh = bitmapHeight;
442        int bw = bitmapWidth;
443        if (GeometryMathUtils.needsDimensionSwap(holder.rotation)) {
444            bh = bitmapWidth;
445            bw = bitmapHeight;
446        }
447        float scale = GeometryMathUtils.scale(bw, bh, viewWidth, viewHeight);
448        scale *= SHOW_SCALE;
449        float s = Math.min(viewWidth / (float) bitmapWidth, viewHeight / (float) bitmapHeight);
450        Matrix m = getFullGeometryMatrix(holder, bitmapWidth, bitmapHeight);
451        m.postScale(scale, scale);
452        m.postTranslate(viewWidth / 2f, viewHeight / 2f);
453        return m;
454    }
455
456    public static RectF getTrueCropRect(GeometryHolder holder, int bitmapWidth, int bitmapHeight) {
457        RectF r = new RectF(holder.crop);
458        FilterCropRepresentation.findScaledCrop(r, bitmapWidth, bitmapHeight);
459        float s = holder.straighten;
460        holder.straighten = 0;
461        Matrix m1 = getFullGeometryMatrix(holder, bitmapWidth, bitmapHeight);
462        holder.straighten = s;
463        m1.mapRect(r);
464        return r;
465    }
466
467    public static Matrix getCropSelectionToScreenMatrix(RectF outCrop, GeometryHolder holder,
468            int bitmapWidth, int bitmapHeight, int viewWidth, int viewHeight) {
469        Matrix m = getFullGeometryMatrix(holder, bitmapWidth, bitmapHeight);
470        RectF crop = getTrueCropRect(holder, bitmapWidth, bitmapHeight);
471        float scale = GeometryMathUtils.scale(crop.width(), crop.height(), viewWidth, viewHeight);
472        m.postScale(scale, scale);
473        GeometryMathUtils.scaleRect(crop, scale);
474        m.postTranslate(viewWidth / 2f - crop.centerX(), viewHeight / 2f - crop.centerY());
475        if (outCrop != null) {
476            crop.offset(viewWidth / 2f - crop.centerX(), viewHeight / 2f - crop.centerY());
477            outCrop.set(crop);
478        }
479        return m;
480    }
481
482    public static Matrix getCropSelectionToScreenMatrix(RectF outCrop,
483            Collection<FilterRepresentation> res, int bitmapWidth, int bitmapHeight, int viewWidth,
484            int viewHeight) {
485        GeometryHolder holder = unpackGeometry(res);
486        return getCropSelectionToScreenMatrix(outCrop, holder, bitmapWidth, bitmapHeight,
487                viewWidth, viewHeight);
488    }
489}
490