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 ImageStraighten(Context context)76 public ImageStraighten(Context context) { 77 super(context); 78 } 79 ImageStraighten(Context context, AttributeSet attrs)80 public ImageStraighten(Context context, AttributeSet attrs) { 81 super(context, attrs); 82 } 83 84 @Override attach()85 public void attach() { 86 super.attach(); 87 mGridAlpha = 1f; 88 hidesGrid(mOnStartAnimDelay); 89 } 90 hidesGrid(int delay)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 setFilterStraightenRepresentation(FilterStraightenRepresentation rep)105 public void setFilterStraightenRepresentation(FilterStraightenRepresentation rep) { 106 mLocalRep = (rep == null) ? new FilterStraightenRepresentation() : rep; 107 mInitialAngle = mBaseAngle = mAngle = mLocalRep.getStraighten(); 108 } 109 getFinalRepresentation()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 onTouchEvent(MotionEvent event)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 angleFor(float dx, float dy)159 private static float angleFor(float dx, float dy) { 160 return (float) (Math.atan2(dx, dy) * 180 / Math.PI); 161 } 162 getCurrentTouchAngle()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 computeValue()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 getUntranslatedStraightenCropBounds(RectF outRect, float straightenAngle)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 updateCurrentCrop(Matrix m, GeometryHolder h, RectF tmp, int imageWidth, int imageHeight, int viewWidth, int viewHeight)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 onDraw(Canvas canvas)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 setEditor(EditorStraighten editorStraighten)306 public void setEditor(EditorStraighten editorStraighten) { 307 mEditorStraighten = editorStraighten; 308 } 309 310 } 311