| /* |
| * Copyright (C) 2012 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.gallery3d.filtershow.imageshow; |
| |
| import android.graphics.Bitmap; |
| import android.graphics.Canvas; |
| import android.graphics.Matrix; |
| import android.graphics.Paint; |
| import android.graphics.Rect; |
| import android.graphics.RectF; |
| import android.util.Log; |
| |
| import com.android.gallery3d.filtershow.cache.BitmapCache; |
| import com.android.gallery3d.filtershow.cache.ImageLoader; |
| import com.android.gallery3d.filtershow.filters.FilterCropRepresentation; |
| import com.android.gallery3d.filtershow.filters.FilterMirrorRepresentation; |
| import com.android.gallery3d.filtershow.filters.FilterMirrorRepresentation.Mirror; |
| import com.android.gallery3d.filtershow.filters.FilterRepresentation; |
| import com.android.gallery3d.filtershow.filters.FilterRotateRepresentation; |
| import com.android.gallery3d.filtershow.filters.FilterRotateRepresentation.Rotation; |
| import com.android.gallery3d.filtershow.filters.FilterStraightenRepresentation; |
| import com.android.gallery3d.filtershow.pipeline.ImagePreset; |
| |
| import java.util.Collection; |
| import java.util.Iterator; |
| |
| public final class GeometryMathUtils { |
| private static final String TAG = "GeometryMathUtils"; |
| public static final float SHOW_SCALE = .9f; |
| |
| private GeometryMathUtils() {}; |
| |
| // Holder class for Geometry data. |
| public static final class GeometryHolder { |
| public Rotation rotation = FilterRotateRepresentation.getNil(); |
| public float straighten = FilterStraightenRepresentation.getNil(); |
| public RectF crop = FilterCropRepresentation.getNil(); |
| public Mirror mirror = FilterMirrorRepresentation.getNil(); |
| |
| public void set(GeometryHolder h) { |
| rotation = h.rotation; |
| straighten = h.straighten; |
| crop.set(h.crop); |
| mirror = h.mirror; |
| } |
| |
| public void wipe() { |
| rotation = FilterRotateRepresentation.getNil(); |
| straighten = FilterStraightenRepresentation.getNil(); |
| crop = FilterCropRepresentation.getNil(); |
| mirror = FilterMirrorRepresentation.getNil(); |
| } |
| |
| public boolean isNil() { |
| return rotation == FilterRotateRepresentation.getNil() && |
| straighten == FilterStraightenRepresentation.getNil() && |
| crop.equals(FilterCropRepresentation.getNil()) && |
| mirror == FilterMirrorRepresentation.getNil(); |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) { |
| return true; |
| } |
| if (!(o instanceof GeometryHolder)) { |
| return false; |
| } |
| GeometryHolder h = (GeometryHolder) o; |
| return rotation == h.rotation && straighten == h.straighten && |
| ((crop == null && h.crop == null) || (crop != null && crop.equals(h.crop))) && |
| mirror == h.mirror; |
| } |
| |
| @Override |
| public String toString() { |
| return getClass().getSimpleName() + "[" + "rotation:" + rotation.value() |
| + ",straighten:" + straighten + ",crop:" + crop.toString() |
| + ",mirror:" + mirror.value() + "]"; |
| } |
| } |
| |
| // Math operations for 2d vectors |
| public static float clamp(float i, float low, float high) { |
| return Math.max(Math.min(i, high), low); |
| } |
| |
| public static float[] lineIntersect(float[] line1, float[] line2) { |
| float a0 = line1[0]; |
| float a1 = line1[1]; |
| float b0 = line1[2]; |
| float b1 = line1[3]; |
| float c0 = line2[0]; |
| float c1 = line2[1]; |
| float d0 = line2[2]; |
| float d1 = line2[3]; |
| float t0 = a0 - b0; |
| float t1 = a1 - b1; |
| float t2 = b0 - d0; |
| float t3 = d1 - b1; |
| float t4 = c0 - d0; |
| float t5 = c1 - d1; |
| |
| float denom = t1 * t4 - t0 * t5; |
| if (denom == 0) |
| return null; |
| float u = (t3 * t4 + t5 * t2) / denom; |
| float[] intersect = { |
| b0 + u * t0, b1 + u * t1 |
| }; |
| return intersect; |
| } |
| |
| public static float[] shortestVectorFromPointToLine(float[] point, float[] line) { |
| float x1 = line[0]; |
| float x2 = line[2]; |
| float y1 = line[1]; |
| float y2 = line[3]; |
| float xdelt = x2 - x1; |
| float ydelt = y2 - y1; |
| if (xdelt == 0 && ydelt == 0) |
| return null; |
| float u = ((point[0] - x1) * xdelt + (point[1] - y1) * ydelt) |
| / (xdelt * xdelt + ydelt * ydelt); |
| float[] ret = { |
| (x1 + u * (x2 - x1)), (y1 + u * (y2 - y1)) |
| }; |
| float[] vec = { |
| ret[0] - point[0], ret[1] - point[1] |
| }; |
| return vec; |
| } |
| |
| // A . B |
| public static float dotProduct(float[] a, float[] b) { |
| return a[0] * b[0] + a[1] * b[1]; |
| } |
| |
| public static float[] normalize(float[] a) { |
| float length = (float) Math.hypot(a[0], a[1]); |
| float[] b = { |
| a[0] / length, a[1] / length |
| }; |
| return b; |
| } |
| |
| // A onto B |
| public static float scalarProjection(float[] a, float[] b) { |
| float length = (float) Math.hypot(b[0], b[1]); |
| return dotProduct(a, b) / length; |
| } |
| |
| public static float[] getVectorFromPoints(float[] point1, float[] point2) { |
| float[] p = { |
| point2[0] - point1[0], point2[1] - point1[1] |
| }; |
| return p; |
| } |
| |
| public static float[] getUnitVectorFromPoints(float[] point1, float[] point2) { |
| float[] p = { |
| point2[0] - point1[0], point2[1] - point1[1] |
| }; |
| float length = (float) Math.hypot(p[0], p[1]); |
| p[0] = p[0] / length; |
| p[1] = p[1] / length; |
| return p; |
| } |
| |
| public static void scaleRect(RectF r, float scale) { |
| r.set(r.left * scale, r.top * scale, r.right * scale, r.bottom * scale); |
| } |
| |
| // A - B |
| public static float[] vectorSubtract(float[] a, float[] b) { |
| int len = a.length; |
| if (len != b.length) |
| return null; |
| float[] ret = new float[len]; |
| for (int i = 0; i < len; i++) { |
| ret[i] = a[i] - b[i]; |
| } |
| return ret; |
| } |
| |
| public static float vectorLength(float[] a) { |
| return (float) Math.hypot(a[0], a[1]); |
| } |
| |
| public static float scale(float oldWidth, float oldHeight, float newWidth, float newHeight) { |
| if (oldHeight == 0 || oldWidth == 0 || (oldWidth == newWidth && oldHeight == newHeight)) { |
| return 1; |
| } |
| return Math.min(newWidth / oldWidth, newHeight / oldHeight); |
| } |
| |
| public static Rect roundNearest(RectF r) { |
| Rect q = new Rect(Math.round(r.left), Math.round(r.top), Math.round(r.right), |
| Math.round(r.bottom)); |
| return q; |
| } |
| |
| private static void concatMirrorMatrix(Matrix m, GeometryHolder holder) { |
| Mirror type = holder.mirror; |
| if (type == Mirror.HORIZONTAL) { |
| if (holder.rotation.value() == 90 |
| || holder.rotation.value() == 270) { |
| type = Mirror.VERTICAL; |
| } |
| } else if (type == Mirror.VERTICAL) { |
| if (holder.rotation.value() == 90 |
| || holder.rotation.value() == 270) { |
| type = Mirror.HORIZONTAL; |
| } |
| } |
| if (type == Mirror.HORIZONTAL) { |
| m.postScale(-1, 1); |
| } else if (type == Mirror.VERTICAL) { |
| m.postScale(1, -1); |
| } else if (type == Mirror.BOTH) { |
| m.postScale(1, -1); |
| m.postScale(-1, 1); |
| } |
| } |
| |
| private static int getRotationForOrientation(int orientation) { |
| switch (orientation) { |
| case ImageLoader.ORI_ROTATE_90: |
| return 90; |
| case ImageLoader.ORI_ROTATE_180: |
| return 180; |
| case ImageLoader.ORI_ROTATE_270: |
| return 270; |
| default: |
| return 0; |
| } |
| } |
| |
| public static GeometryHolder unpackGeometry(Collection<FilterRepresentation> geometry) { |
| GeometryHolder holder = new GeometryHolder(); |
| unpackGeometry(holder, geometry); |
| return holder; |
| } |
| |
| public static void unpackGeometry(GeometryHolder out, |
| Collection<FilterRepresentation> geometry) { |
| out.wipe(); |
| // Get geometry data from filters |
| for (FilterRepresentation r : geometry) { |
| if (r.isNil()) { |
| continue; |
| } |
| if (r.getSerializationName() == FilterRotateRepresentation.SERIALIZATION_NAME) { |
| out.rotation = ((FilterRotateRepresentation) r).getRotation(); |
| } else if (r.getSerializationName() == |
| FilterStraightenRepresentation.SERIALIZATION_NAME) { |
| out.straighten = ((FilterStraightenRepresentation) r).getStraighten(); |
| } else if (r.getSerializationName() == FilterCropRepresentation.SERIALIZATION_NAME) { |
| ((FilterCropRepresentation) r).getCrop(out.crop); |
| } else if (r.getSerializationName() == FilterMirrorRepresentation.SERIALIZATION_NAME) { |
| out.mirror = ((FilterMirrorRepresentation) r).getMirror(); |
| } |
| } |
| } |
| |
| public static void replaceInstances(Collection<FilterRepresentation> geometry, |
| FilterRepresentation rep) { |
| Iterator<FilterRepresentation> iter = geometry.iterator(); |
| while (iter.hasNext()) { |
| FilterRepresentation r = iter.next(); |
| if (ImagePreset.sameSerializationName(rep, r)) { |
| iter.remove(); |
| } |
| } |
| if (!rep.isNil()) { |
| geometry.add(rep); |
| } |
| } |
| |
| public static void initializeHolder(GeometryHolder outHolder, |
| FilterRepresentation currentLocal) { |
| Collection<FilterRepresentation> geometry = MasterImage.getImage().getPreset() |
| .getGeometryFilters(); |
| replaceInstances(geometry, currentLocal); |
| unpackGeometry(outHolder, geometry); |
| } |
| |
| public static Rect finalGeometryRect(int width, int height, |
| Collection<FilterRepresentation> geometry) { |
| GeometryHolder holder = unpackGeometry(geometry); |
| RectF crop = getTrueCropRect(holder, width, height); |
| Rect frame = new Rect(); |
| crop.roundOut(frame); |
| return frame; |
| } |
| |
| private static Bitmap applyFullGeometryMatrix(Bitmap image, GeometryHolder holder) { |
| int width = image.getWidth(); |
| int height = image.getHeight(); |
| RectF crop = getTrueCropRect(holder, width, height); |
| Rect frame = new Rect(); |
| crop.roundOut(frame); |
| Matrix m = getCropSelectionToScreenMatrix(null, holder, width, height, frame.width(), |
| frame.height()); |
| BitmapCache bitmapCache = MasterImage.getImage().getBitmapCache(); |
| Bitmap temp = bitmapCache.getBitmap(frame.width(), |
| frame.height(), BitmapCache.UTIL_GEOMETRY); |
| Canvas canvas = new Canvas(temp); |
| Paint paint = new Paint(); |
| paint.setAntiAlias(true); |
| paint.setFilterBitmap(true); |
| paint.setDither(true); |
| canvas.drawBitmap(image, m, paint); |
| return temp; |
| } |
| |
| public static Matrix getImageToScreenMatrix(Collection<FilterRepresentation> geometry, |
| boolean reflectRotation, Rect bmapDimens, float viewWidth, float viewHeight) { |
| GeometryHolder h = unpackGeometry(geometry); |
| return GeometryMathUtils.getOriginalToScreen(h, reflectRotation, bmapDimens.width(), |
| bmapDimens.height(), viewWidth, viewHeight); |
| } |
| |
| public static Matrix getPartialToScreenMatrix(Collection<FilterRepresentation> geometry, |
| Rect originalBounds, float w, float h, |
| float pw, float ph) { |
| GeometryHolder holder = unpackGeometry(geometry); |
| RectF rCrop = new RectF(0, 0, originalBounds.width(), originalBounds.height()); |
| float angle = holder.straighten; |
| int rotation = holder.rotation.value(); |
| |
| ImageStraighten.getUntranslatedStraightenCropBounds(rCrop, angle); |
| float dx = (w - pw) / 2f; |
| float dy = (h - ph) / 2f; |
| Matrix compensation = new Matrix(); |
| compensation.postTranslate(dx, dy); |
| float cScale = originalBounds.width() / rCrop.width(); |
| if (rCrop.width() < rCrop.height()) { |
| cScale = originalBounds.height() / rCrop.height(); |
| } |
| float scale = w / pw; |
| if (w < h) { |
| scale = h / ph; |
| } |
| scale = scale * cScale; |
| float cx = w / 2f; |
| float cy = h / 2f; |
| |
| compensation.postScale(scale, scale, cx, cy); |
| compensation.postRotate(angle, cx, cy); |
| compensation.postRotate(rotation, cx, cy); |
| compensation.postTranslate(-cx, -cy); |
| concatMirrorMatrix(compensation, holder); |
| compensation.postTranslate(cx, cy); |
| return compensation; |
| } |
| |
| public static Matrix getOriginalToScreen(GeometryHolder holder, boolean rotate, |
| float originalWidth, |
| float originalHeight, float viewWidth, float viewHeight) { |
| int orientation = MasterImage.getImage().getZoomOrientation(); |
| int rotation = getRotationForOrientation(orientation); |
| Rotation prev = holder.rotation; |
| rotation = (rotation + prev.value()) % 360; |
| holder.rotation = Rotation.fromValue(rotation); |
| Matrix m = getCropSelectionToScreenMatrix(null, holder, (int) originalWidth, |
| (int) originalHeight, (int) viewWidth, (int) viewHeight); |
| holder.rotation = prev; |
| return m; |
| } |
| |
| public static Bitmap applyGeometryRepresentations(Collection<FilterRepresentation> res, |
| Bitmap image) { |
| GeometryHolder holder = unpackGeometry(res); |
| Bitmap bmap = image; |
| // If there are geometry changes, apply them to the image |
| if (!holder.isNil()) { |
| bmap = applyFullGeometryMatrix(bmap, holder); |
| if (bmap != image) { |
| BitmapCache cache = MasterImage.getImage().getBitmapCache(); |
| cache.cache(image); |
| } |
| } |
| return bmap; |
| } |
| |
| public static RectF drawTransformedCropped(GeometryHolder holder, Canvas canvas, |
| Bitmap photo, int viewWidth, int viewHeight) { |
| if (photo == null) { |
| return null; |
| } |
| RectF crop = new RectF(); |
| Matrix m = getCropSelectionToScreenMatrix(crop, holder, photo.getWidth(), photo.getHeight(), |
| viewWidth, viewHeight); |
| canvas.save(); |
| canvas.clipRect(crop); |
| Paint p = new Paint(); |
| p.setAntiAlias(true); |
| canvas.drawBitmap(photo, m, p); |
| canvas.restore(); |
| return crop; |
| } |
| |
| public static boolean needsDimensionSwap(Rotation rotation) { |
| switch (rotation) { |
| case NINETY: |
| case TWO_SEVENTY: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| // Gives matrix for rotated, straightened, mirrored bitmap centered at 0,0. |
| private static Matrix getFullGeometryMatrix(GeometryHolder holder, int bitmapWidth, |
| int bitmapHeight) { |
| float centerX = bitmapWidth / 2f; |
| float centerY = bitmapHeight / 2f; |
| Matrix m = new Matrix(); |
| m.setTranslate(-centerX, -centerY); |
| m.postRotate(holder.straighten + holder.rotation.value()); |
| concatMirrorMatrix(m, holder); |
| return m; |
| } |
| |
| public static Matrix getFullGeometryToScreenMatrix(GeometryHolder holder, int bitmapWidth, |
| int bitmapHeight, int viewWidth, int viewHeight) { |
| int bh = bitmapHeight; |
| int bw = bitmapWidth; |
| if (GeometryMathUtils.needsDimensionSwap(holder.rotation)) { |
| bh = bitmapWidth; |
| bw = bitmapHeight; |
| } |
| float scale = GeometryMathUtils.scale(bw, bh, viewWidth, viewHeight); |
| scale *= SHOW_SCALE; |
| float s = Math.min(viewWidth / (float) bitmapWidth, viewHeight / (float) bitmapHeight); |
| Matrix m = getFullGeometryMatrix(holder, bitmapWidth, bitmapHeight); |
| m.postScale(scale, scale); |
| m.postTranslate(viewWidth / 2f, viewHeight / 2f); |
| return m; |
| } |
| |
| public static RectF getTrueCropRect(GeometryHolder holder, int bitmapWidth, int bitmapHeight) { |
| RectF r = new RectF(holder.crop); |
| FilterCropRepresentation.findScaledCrop(r, bitmapWidth, bitmapHeight); |
| float s = holder.straighten; |
| holder.straighten = 0; |
| Matrix m1 = getFullGeometryMatrix(holder, bitmapWidth, bitmapHeight); |
| holder.straighten = s; |
| m1.mapRect(r); |
| return r; |
| } |
| |
| public static Matrix getCropSelectionToScreenMatrix(RectF outCrop, GeometryHolder holder, |
| int bitmapWidth, int bitmapHeight, int viewWidth, int viewHeight) { |
| Matrix m = getFullGeometryMatrix(holder, bitmapWidth, bitmapHeight); |
| RectF crop = getTrueCropRect(holder, bitmapWidth, bitmapHeight); |
| float scale = GeometryMathUtils.scale(crop.width(), crop.height(), viewWidth, viewHeight); |
| m.postScale(scale, scale); |
| GeometryMathUtils.scaleRect(crop, scale); |
| m.postTranslate(viewWidth / 2f - crop.centerX(), viewHeight / 2f - crop.centerY()); |
| if (outCrop != null) { |
| crop.offset(viewWidth / 2f - crop.centerX(), viewHeight / 2f - crop.centerY()); |
| outCrop.set(crop); |
| } |
| return m; |
| } |
| |
| public static Matrix getCropSelectionToScreenMatrix(RectF outCrop, |
| Collection<FilterRepresentation> res, int bitmapWidth, int bitmapHeight, int viewWidth, |
| int viewHeight) { |
| GeometryHolder holder = unpackGeometry(res); |
| return getCropSelectionToScreenMatrix(outCrop, holder, bitmapWidth, bitmapHeight, |
| viewWidth, viewHeight); |
| } |
| } |