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.graphics.Bitmap;
20 import android.graphics.Canvas;
21 import android.graphics.Matrix;
22 import android.graphics.Paint;
23 import android.graphics.Rect;
24 import android.graphics.RectF;
25 import android.util.Log;
26 
27 import com.android.gallery3d.filtershow.cache.BitmapCache;
28 import com.android.gallery3d.filtershow.cache.ImageLoader;
29 import com.android.gallery3d.filtershow.filters.FilterCropRepresentation;
30 import com.android.gallery3d.filtershow.filters.FilterMirrorRepresentation;
31 import com.android.gallery3d.filtershow.filters.FilterMirrorRepresentation.Mirror;
32 import com.android.gallery3d.filtershow.filters.FilterRepresentation;
33 import com.android.gallery3d.filtershow.filters.FilterRotateRepresentation;
34 import com.android.gallery3d.filtershow.filters.FilterRotateRepresentation.Rotation;
35 import com.android.gallery3d.filtershow.filters.FilterStraightenRepresentation;
36 import com.android.gallery3d.filtershow.pipeline.ImagePreset;
37 
38 import java.util.Collection;
39 import java.util.Iterator;
40 
41 public final class GeometryMathUtils {
42     private static final String TAG = "GeometryMathUtils";
43     public static final float SHOW_SCALE = .9f;
44 
GeometryMathUtils()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 
set(GeometryHolder h)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 
wipe()61         public void wipe() {
62             rotation = FilterRotateRepresentation.getNil();
63             straighten = FilterStraightenRepresentation.getNil();
64             crop = FilterCropRepresentation.getNil();
65             mirror = FilterMirrorRepresentation.getNil();
66         }
67 
isNil()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
equals(Object o)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
toString()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
clamp(float i, float low, float high)98     public static float clamp(float i, float low, float high) {
99         return Math.max(Math.min(i, high), low);
100     }
101 
lineIntersect(float[] line1, float[] line2)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 
shortestVectorFromPointToLine(float[] point, float[] line)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
dotProduct(float[] a, float[] b)149     public static float dotProduct(float[] a, float[] b) {
150         return a[0] * b[0] + a[1] * b[1];
151     }
152 
normalize(float[] a)153     public static float[] normalize(float[] a) {
154         float length = (float) Math.hypot(a[0], a[1]);
155         float[] b = {
156                 a[0] / length, a[1] / length
157         };
158         return b;
159     }
160 
161     // A onto B
scalarProjection(float[] a, float[] b)162     public static float scalarProjection(float[] a, float[] b) {
163         float length = (float) Math.hypot(b[0], b[1]);
164         return dotProduct(a, b) / length;
165     }
166 
getVectorFromPoints(float[] point1, float[] point2)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 
getUnitVectorFromPoints(float[] point1, float[] point2)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.hypot(p[0], p[1]);
179         p[0] = p[0] / length;
180         p[1] = p[1] / length;
181         return p;
182     }
183 
scaleRect(RectF r, float scale)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
vectorSubtract(float[] a, float[] 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 
vectorLength(float[] a)200     public static float vectorLength(float[] a) {
201         return (float) Math.hypot(a[0], a[1]);
202     }
203 
scale(float oldWidth, float oldHeight, float newWidth, float newHeight)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 
roundNearest(RectF r)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 
concatMirrorMatrix(Matrix m, GeometryHolder holder)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 
getRotationForOrientation(int orientation)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 
unpackGeometry(Collection<FilterRepresentation> geometry)253     public static GeometryHolder unpackGeometry(Collection<FilterRepresentation> geometry) {
254         GeometryHolder holder = new GeometryHolder();
255         unpackGeometry(holder, geometry);
256         return holder;
257     }
258 
unpackGeometry(GeometryHolder out, Collection<FilterRepresentation> geometry)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 
replaceInstances(Collection<FilterRepresentation> geometry, FilterRepresentation rep)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 
initializeHolder(GeometryHolder outHolder, FilterRepresentation currentLocal)294     public static void initializeHolder(GeometryHolder outHolder,
295             FilterRepresentation currentLocal) {
296         Collection<FilterRepresentation> geometry = PrimaryImage.getImage().getPreset()
297                 .getGeometryFilters();
298         replaceInstances(geometry, currentLocal);
299         unpackGeometry(outHolder, geometry);
300     }
301 
finalGeometryRect(int width, int height, Collection<FilterRepresentation> geometry)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 
applyFullGeometryMatrix(Bitmap image, GeometryHolder holder)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 = PrimaryImage.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 
getImageToScreenMatrix(Collection<FilterRepresentation> geometry, boolean reflectRotation, Rect bmapDimens, float viewWidth, float viewHeight)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 
getPartialToScreenMatrix(Collection<FilterRepresentation> geometry, Rect originalBounds, float w, float h, float pw, float ph)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 
getOriginalToScreen(GeometryHolder holder, boolean rotate, float originalWidth, float originalHeight, float viewWidth, float viewHeight)372     public static Matrix getOriginalToScreen(GeometryHolder holder, boolean rotate,
373             float originalWidth,
374             float originalHeight, float viewWidth, float viewHeight) {
375         int orientation = PrimaryImage.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 
applyGeometryRepresentations(Collection<FilterRepresentation> res, Bitmap image)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 = PrimaryImage.getImage().getBitmapCache();
395                 cache.cache(image);
396             }
397         }
398         return bmap;
399     }
400 
drawTransformedCropped(GeometryHolder holder, Canvas canvas, Bitmap photo, int viewWidth, int viewHeight)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 
needsDimensionSwap(Rotation rotation)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.
getFullGeometryMatrix(GeometryHolder holder, int bitmapWidth, int bitmapHeight)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 
getFullGeometryToScreenMatrix(GeometryHolder holder, int bitmapWidth, int bitmapHeight, int viewWidth, int viewHeight)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 
getTrueCropRect(GeometryHolder holder, int bitmapWidth, int bitmapHeight)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 
getCropSelectionToScreenMatrix(RectF outCrop, GeometryHolder holder, int bitmapWidth, int bitmapHeight, int viewWidth, int viewHeight)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 
getCropSelectionToScreenMatrix(RectF outCrop, Collection<FilterRepresentation> res, int bitmapWidth, int bitmapHeight, int viewWidth, int viewHeight)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