Home | History | Annotate | Download | only in imageshow
      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 
     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.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
    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 
    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.hypot(p[0], 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.hypot(a[0], 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