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.animation.ValueAnimator; 20 import android.content.Context; 21 import android.graphics.Bitmap; 22 import android.graphics.Canvas; 23 import android.graphics.Color; 24 import android.graphics.Matrix; 25 import android.graphics.Paint; 26 import android.graphics.Paint.Style; 27 import android.graphics.Path; 28 import android.graphics.RectF; 29 import android.util.AttributeSet; 30 import android.view.MotionEvent; 31 32 import com.android.gallery3d.filtershow.crop.CropDrawingUtils; 33 import com.android.gallery3d.filtershow.editors.EditorStraighten; 34 import com.android.gallery3d.filtershow.filters.FilterCropRepresentation; 35 import com.android.gallery3d.filtershow.filters.FilterRepresentation; 36 import com.android.gallery3d.filtershow.filters.FilterStraightenRepresentation; 37 import com.android.gallery3d.filtershow.imageshow.GeometryMathUtils.GeometryHolder; 38 39 import java.util.ArrayList; 40 import java.util.Collection; 41 42 43 public class ImageStraighten extends ImageShow { 44 private static final String TAG = ImageStraighten.class.getSimpleName(); 45 private float mBaseAngle = 0; 46 private float mAngle = 0; 47 private float mInitialAngle = 0; 48 private static final int NBLINES = 16; 49 private boolean mFirstDrawSinceUp = false; 50 private EditorStraighten mEditorStraighten; 51 private FilterStraightenRepresentation mLocalRep = new FilterStraightenRepresentation(); 52 private RectF mPriorCropAtUp = new RectF(); 53 private RectF mDrawRect = new RectF(); 54 private Path mDrawPath = new Path(); 55 private GeometryHolder mDrawHolder = new GeometryHolder(); 56 private enum MODES { 57 NONE, MOVE 58 } 59 private MODES mState = MODES.NONE; 60 private ValueAnimator mAnimator = null; 61 private int mDefaultGridAlpha = 60; 62 private float mGridAlpha = 1f; 63 private int mOnStartAnimDelay = 1000; 64 private int mAnimDelay = 500; 65 private static final float MAX_STRAIGHTEN_ANGLE 66 = FilterStraightenRepresentation.MAX_STRAIGHTEN_ANGLE; 67 private static final float MIN_STRAIGHTEN_ANGLE 68 = FilterStraightenRepresentation.MIN_STRAIGHTEN_ANGLE; 69 private float mCurrentX; 70 private float mCurrentY; 71 private float mTouchCenterX; 72 private float mTouchCenterY; 73 private RectF mCrop = new RectF(); 74 private final Paint mPaint = new Paint(); 75 76 public ImageStraighten(Context context) { 77 super(context); 78 } 79 80 public ImageStraighten(Context context, AttributeSet attrs) { 81 super(context, attrs); 82 } 83 84 @Override 85 public void attach() { 86 super.attach(); 87 mGridAlpha = 1f; 88 hidesGrid(mOnStartAnimDelay); 89 } 90 91 private void hidesGrid(int delay) { 92 mAnimator = ValueAnimator.ofFloat(1, 0); 93 mAnimator.setStartDelay(delay); 94 mAnimator.setDuration(mAnimDelay); 95 mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 96 @Override 97 public void onAnimationUpdate(ValueAnimator animation) { 98 mGridAlpha = ((Float) animation.getAnimatedValue()); 99 invalidate(); 100 } 101 }); 102 mAnimator.start(); 103 } 104 105 public void setFilterStraightenRepresentation(FilterStraightenRepresentation rep) { 106 mLocalRep = (rep == null) ? new FilterStraightenRepresentation() : rep; 107 mInitialAngle = mBaseAngle = mAngle = mLocalRep.getStraighten(); 108 } 109 110 public Collection<FilterRepresentation> getFinalRepresentation() { 111 ArrayList<FilterRepresentation> reps = new ArrayList<FilterRepresentation>(2); 112 reps.add(mLocalRep); 113 if (mInitialAngle != mLocalRep.getStraighten()) { 114 reps.add(new FilterCropRepresentation(mCrop)); 115 } 116 return reps; 117 } 118 119 @Override 120 public boolean onTouchEvent(MotionEvent event) { 121 float x = event.getX(); 122 float y = event.getY(); 123 124 switch (event.getActionMasked()) { 125 case (MotionEvent.ACTION_DOWN): 126 if (mState == MODES.NONE) { 127 mTouchCenterX = x; 128 mTouchCenterY = y; 129 mCurrentX = x; 130 mCurrentY = y; 131 mState = MODES.MOVE; 132 mBaseAngle = mAngle; 133 } 134 break; 135 case (MotionEvent.ACTION_UP): 136 if (mState == MODES.MOVE) { 137 mState = MODES.NONE; 138 mCurrentX = x; 139 mCurrentY = y; 140 computeValue(); 141 mFirstDrawSinceUp = true; 142 hidesGrid(0); 143 } 144 break; 145 case (MotionEvent.ACTION_MOVE): 146 if (mState == MODES.MOVE) { 147 mCurrentX = x; 148 mCurrentY = y; 149 computeValue(); 150 } 151 break; 152 default: 153 break; 154 } 155 invalidate(); 156 return true; 157 } 158 159 private static float angleFor(float dx, float dy) { 160 return (float) (Math.atan2(dx, dy) * 180 / Math.PI); 161 } 162 163 private float getCurrentTouchAngle() { 164 float centerX = getWidth() / 2f; 165 float centerY = getHeight() / 2f; 166 if (mCurrentX == mTouchCenterX && mCurrentY == mTouchCenterY) { 167 return 0; 168 } 169 float dX1 = mTouchCenterX - centerX; 170 float dY1 = mTouchCenterY - centerY; 171 float dX2 = mCurrentX - centerX; 172 float dY2 = mCurrentY - centerY; 173 float angleA = angleFor(dX1, dY1); 174 float angleB = angleFor(dX2, dY2); 175 return (angleB - angleA) % 360; 176 } 177 178 private void computeValue() { 179 float angle = getCurrentTouchAngle(); 180 mAngle = (mBaseAngle - angle) % 360; 181 mAngle = Math.max(MIN_STRAIGHTEN_ANGLE, mAngle); 182 mAngle = Math.min(MAX_STRAIGHTEN_ANGLE, mAngle); 183 } 184 185 public static void getUntranslatedStraightenCropBounds(RectF outRect, float straightenAngle) { 186 float deg = straightenAngle; 187 if (deg < 0) { 188 deg = -deg; 189 } 190 double a = Math.toRadians(deg); 191 double sina = Math.sin(a); 192 double cosa = Math.cos(a); 193 double rw = outRect.width(); 194 double rh = outRect.height(); 195 double h1 = rh * rh / (rw * sina + rh * cosa); 196 double h2 = rh * rw / (rw * cosa + rh * sina); 197 double hh = Math.min(h1, h2); 198 double ww = hh * rw / rh; 199 float left = (float) ((rw - ww) * 0.5f); 200 float top = (float) ((rh - hh) * 0.5f); 201 float right = (float) (left + ww); 202 float bottom = (float) (top + hh); 203 outRect.set(left, top, right, bottom); 204 } 205 206 private void updateCurrentCrop(Matrix m, GeometryHolder h, RectF tmp, int imageWidth, 207 int imageHeight, int viewWidth, int viewHeight) { 208 tmp.set(0, 0, imageHeight, imageWidth); 209 m.mapRect(tmp); 210 float top = tmp.top; 211 float bottom = tmp.bottom; 212 float left = tmp.left; 213 float right = tmp.right; 214 m.mapRect(tmp); 215 int iw,ih; 216 if (GeometryMathUtils.needsDimensionSwap(h.rotation)) { 217 tmp.set(0, 0, imageHeight, imageWidth); 218 iw = imageHeight; 219 ih = imageWidth; 220 } else { 221 tmp.set(0, 0, imageWidth, imageHeight); 222 iw = imageWidth; 223 ih = imageHeight; 224 } 225 float scale = GeometryMathUtils.scale(iw, ih, viewWidth, viewHeight); 226 scale *= GeometryMathUtils.SHOW_SCALE; 227 GeometryMathUtils.scaleRect(tmp, scale); 228 getUntranslatedStraightenCropBounds(tmp, mAngle); 229 tmp.offset(viewWidth / 2f - tmp.centerX(), viewHeight / 2f - tmp.centerY()); 230 h.straighten = 0; 231 Matrix m1 = GeometryMathUtils.getFullGeometryToScreenMatrix(h, imageWidth, 232 imageHeight, viewWidth, viewHeight); 233 m.reset(); 234 m1.invert(m); 235 mCrop.set(tmp); 236 m.mapRect(mCrop); 237 FilterCropRepresentation.findNormalizedCrop(mCrop, imageWidth, imageHeight); 238 } 239 240 241 @Override 242 public void onDraw(Canvas canvas) { 243 MasterImage master = MasterImage.getImage(); 244 Bitmap image = master.getFiltersOnlyImage(); 245 if (image == null) { 246 MasterImage.getImage().invalidateFiltersOnly(); 247 return; 248 } 249 GeometryMathUtils.initializeHolder(mDrawHolder, mLocalRep); 250 mDrawHolder.straighten = mAngle; 251 int imageWidth = image.getWidth(); 252 int imageHeight = image.getHeight(); 253 int viewWidth = canvas.getWidth(); 254 int viewHeight = canvas.getHeight(); 255 256 // Get matrix for drawing bitmap 257 Matrix m = GeometryMathUtils.getFullGeometryToScreenMatrix(mDrawHolder, imageWidth, 258 imageHeight, viewWidth, viewHeight); 259 mPaint.reset(); 260 mPaint.setAntiAlias(true); 261 mPaint.setFilterBitmap(true); 262 canvas.drawBitmap(image, m, mPaint); 263 264 mPaint.setFilterBitmap(false); 265 mPaint.setColor(Color.WHITE); 266 mPaint.setStrokeWidth(2); 267 mPaint.setStyle(Paint.Style.FILL_AND_STROKE); 268 updateCurrentCrop(m, mDrawHolder, mDrawRect, imageWidth, 269 imageHeight, viewWidth, viewHeight); 270 if (mFirstDrawSinceUp) { 271 mPriorCropAtUp.set(mCrop); 272 mLocalRep.setStraighten(mAngle); 273 mFirstDrawSinceUp = false; 274 } 275 CropDrawingUtils.drawShade(canvas, mDrawRect); 276 // Draw the grid 277 if (mState == MODES.MOVE || mGridAlpha > 0) { 278 canvas.save(); 279 canvas.clipRect(mDrawRect); 280 281 float step = Math.max(viewWidth, viewHeight) / NBLINES; 282 float p = 0; 283 for (int i = 1; i < NBLINES; i++) { 284 p = i * step; 285 int alpha = (int) (mDefaultGridAlpha * mGridAlpha); 286 if (alpha == 0 && mState == MODES.MOVE) { 287 alpha = mDefaultGridAlpha; 288 } 289 mPaint.setAlpha(alpha); 290 canvas.drawLine(p, 0, p, viewHeight, mPaint); 291 canvas.drawLine(0, p, viewWidth, p, mPaint); 292 } 293 canvas.restore(); 294 } 295 mPaint.reset(); 296 mPaint.setColor(Color.WHITE); 297 mPaint.setStyle(Style.STROKE); 298 mPaint.setStrokeWidth(3); 299 mDrawPath.reset(); 300 301 302 mDrawPath.addRect(mDrawRect, Path.Direction.CW); 303 canvas.drawPath(mDrawPath, mPaint); 304 } 305 306 public void setEditor(EditorStraighten editorStraighten) { 307 mEditorStraighten = editorStraighten; 308 } 309 310 } 311