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