blob: 3b2a704d77b6a3b9ba63f3bedcdde97dc35c7afd [file] [log] [blame]
/*
* 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.Color;
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);
}
}
public 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);
temp.eraseColor(Color.TRANSPARENT);
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) {
return getOriginalToScreen(holder, null, rotate, originalWidth, originalHeight, viewWidth, viewHeight);
}
public static Matrix getOriginalToScreen(GeometryHolder holder, RectF outCrop,
boolean rotate, float originalWidth, float originalHeight,
float viewWidth, float viewHeight) {
RectF crop = new RectF();
Matrix m;
int orientation = MasterImage.getImage().getZoomOrientation();
if (orientation == ImageLoader.ORI_ROTATE_90 ||
orientation == ImageLoader.ORI_ROTATE_270 ||
orientation == ImageLoader.ORI_TRANSPOSE ||
orientation == ImageLoader.ORI_TRANSVERSE) {
crop = getTrueCropRect(holder, (int)originalHeight, (int)originalWidth);
} else {
crop = getTrueCropRect(holder, (int)originalWidth, (int)originalHeight);
}
float scale = scale(crop.width(), crop.height(), viewWidth, viewHeight);
m = getFullGeometryMatrix(holder, (int)originalWidth, (int)originalHeight);
if(orientation != ImageLoader.ORI_NORMAL) {
addOrientationToMatrix(m, (int)originalWidth, (int)originalHeight, orientation);
}
m.postScale(scale, scale);
GeometryMathUtils.scaleRect(crop, scale);
m.postTranslate(viewWidth / 2f - crop.centerX(), viewHeight / 2f - crop.centerY());
crop.offset(viewWidth / 2f - crop.centerX(), viewHeight / 2f - crop.centerY());
if(outCrop != null) {
outCrop.set(crop);
}
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.
public 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);
if (scale > 3.0f) {
scale = 3.0f;
}
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 Matrix getSegMatrix(GeometryHolder holder, int bitmapWidth,
int bitmapHeight, int viewWidth, int viewHeight
, int oldw, int oldh) {
int bh = bitmapHeight;
int bw = bitmapWidth;
if (GeometryMathUtils.needsDimensionSwap(holder.rotation)) {
bh = bitmapWidth;
bw = bitmapHeight;
}
float oldScale = GeometryMathUtils.scale(bw, bh, oldw, oldh);
if (oldScale > 3.0f) {
oldScale = 3.0f;
}
oldScale *= SHOW_SCALE;
float scale = GeometryMathUtils.scale(bw, bh, viewWidth, viewHeight);
if (scale > 3.0f) {
scale = 3.0f;
}
scale *= SHOW_SCALE;
Matrix m = new Matrix();
m.setTranslate(-oldw / 2f, -oldh / 2f);
m.postScale(scale / oldScale, scale / oldScale);
m.postTranslate(viewWidth / 2f, viewHeight / 2f);
return m;
}
public static Matrix getWatermarkMatrix(GeometryHolder holder, int bitmapWidth,
int bitmapHeight, int viewWidth, int viewHeight
, int oldw, int oldh) {
int bh = bitmapHeight;
int bw = bitmapWidth;
if (GeometryMathUtils.needsDimensionSwap(holder.rotation)) {
bh = bitmapWidth;
bw = bitmapHeight;
}
float scale = GeometryMathUtils.getWatermarkScale(holder,bw, bh, viewWidth, viewHeight, oldw, oldh);
Matrix m = new Matrix();
m.setTranslate(-oldw / 2f, -oldh / 2f);
m.postScale(scale, scale);
m.postTranslate(viewWidth / 2f, viewHeight / 2f);
return m;
}
public static float getWatermarkScale(GeometryHolder holder, int bitmapWidth,
int bitmapHeight, int viewWidth, int viewHeight,
int oldw, int oldh) {
int bh = bitmapHeight;
int bw = bitmapWidth;
if (GeometryMathUtils.needsDimensionSwap(holder.rotation)) {
bh = bitmapWidth;
bw = bitmapHeight;
}
float oldScale = GeometryMathUtils.scale(bw, bh, oldw, oldh);
if (oldScale > 3.0f) {
oldScale = 3.0f;
}
oldScale *= SHOW_SCALE;
float scale = GeometryMathUtils.scale(bw, bh, viewWidth, viewHeight);
if (scale > 3.0f) {
scale = 3.0f;
}
scale *= SHOW_SCALE;
return scale / oldScale;
}
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);
}
public static void addOrientationToMatrix(Matrix matrix, int w, int h, int orientation) {
switch (orientation) {
case ImageLoader.ORI_ROTATE_90:
matrix.preRotate(90, w/2, h/2);
break;
case ImageLoader.ORI_ROTATE_180:
matrix.preRotate(180, w/2, h/2);
break;
case ImageLoader.ORI_ROTATE_270:
matrix.preRotate(270, w/2, h/2);
break;
case ImageLoader.ORI_FLIP_HOR:
matrix.preScale(-1, 1);
break;
case ImageLoader.ORI_FLIP_VERT:
matrix.preScale(1, -1);
break;
case ImageLoader.ORI_TRANSPOSE:
matrix.preScale(1, -1);
matrix.preRotate(90, w / 2f, h / 2f);
break;
case ImageLoader.ORI_TRANSVERSE:
matrix.preScale(1, -1);
matrix.preRotate(270, w / 2f, h / 2f);
break;
}
}
}