1 /* 2 * Copyright (C) 2010 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.photoeditor; 18 19 import android.content.Context; 20 import android.graphics.Canvas; 21 import android.graphics.Matrix; 22 import android.graphics.Paint; 23 import android.graphics.Path; 24 import android.graphics.PointF; 25 import android.graphics.RectF; 26 import android.util.AttributeSet; 27 import android.view.View; 28 import android.view.animation.Animation; 29 30 import com.android.photoeditor.animation.AnimationPair; 31 32 /** 33 * Displays photo in the view. All its methods should be called from UI thread. 34 */ 35 public class PhotoView extends View { 36 37 private final Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG); 38 private final Matrix displayMatrix = new Matrix(); 39 private Photo photo; 40 private RectF clipBounds; 41 private AnimationPair transitions; 42 43 public PhotoView(Context context, AttributeSet attrs) { 44 super(context, attrs); 45 } 46 47 @Override 48 protected void onDraw(Canvas canvas) { 49 super.onDraw(canvas); 50 51 if (photo != null) { 52 canvas.save(); 53 if (clipBounds != null) { 54 canvas.clipRect(clipBounds); 55 } 56 canvas.concat(displayMatrix); 57 canvas.drawBitmap(photo.bitmap(), 0, 0, paint); 58 canvas.restore(); 59 } 60 } 61 62 /** 63 * Maps x and y to a percentage position relative to displayed photo. 64 */ 65 public PointF mapPhotoPoint(float x, float y) { 66 if ((photo == null) || (photo.width() == 0) || (photo.height() == 0)) { 67 return new PointF(); 68 } 69 float[] point = new float[] {x, y}; 70 Matrix matrix = new Matrix(); 71 displayMatrix.invert(matrix); 72 matrix.mapPoints(point); 73 return new PointF(point[0] / photo.width(), point[1] / photo.height()); 74 } 75 76 public void mapPhotoPath(Path src, Path dst) { 77 // TODO: Use percentages representing paths for saving photo larger than previewed photo. 78 Matrix matrix = new Matrix(); 79 displayMatrix.invert(matrix); 80 src.transform(matrix, dst); 81 } 82 83 public RectF getPhotoDisplayBounds() { 84 RectF bounds = getPhotoBounds(); 85 displayMatrix.mapRect(bounds); 86 return bounds; 87 } 88 89 public RectF getPhotoBounds() { 90 return (photo != null) ? new RectF(0, 0, photo.width(), photo.height()) : new RectF(); 91 } 92 93 /** 94 * Transforms display by replacing the display matrix of photo-view with the given matrix. 95 */ 96 public void transformDisplay(Matrix matrix) { 97 RectF bounds = getPhotoBounds(); 98 matrix.mapRect(bounds); 99 displayMatrix.set(matrix); 100 RectUtils.postCenterMatrix(bounds, this, displayMatrix); 101 invalidate(); 102 } 103 104 public void clipPhoto(RectF bounds) { 105 clipBounds = bounds; 106 invalidate(); 107 } 108 109 /** 110 * Updates the photo with animations (if any) and also updates photo display-matrix. 111 */ 112 public void update(final Photo photo) { 113 if (transitions == null) { 114 setPhoto(photo); 115 invalidate(); 116 } else if (getAnimation() != null) { 117 // Clear old running transitions. 118 clearTransitionAnimations(); 119 setPhoto(photo); 120 invalidate(); 121 } else { 122 // TODO: Use AnimationSet to chain two animations. 123 transitions.first().setAnimationListener(new Animation.AnimationListener() { 124 125 @Override 126 public void onAnimationStart(Animation animation) { 127 } 128 129 @Override 130 public void onAnimationRepeat(Animation animation) { 131 } 132 133 @Override 134 public void onAnimationEnd(final Animation animation) { 135 post(new Runnable() { 136 137 @Override 138 public void run() { 139 if ((transitions != null) && (animation == transitions.first())) { 140 startAnimation(transitions.second()); 141 } 142 } 143 }); 144 } 145 }); 146 transitions.second().setAnimationListener(new Animation.AnimationListener() { 147 148 @Override 149 public void onAnimationStart(Animation animation) { 150 setPhoto(photo); 151 } 152 153 @Override 154 public void onAnimationRepeat(Animation animation) { 155 } 156 157 @Override 158 public void onAnimationEnd(Animation animation) { 159 post(new Runnable() { 160 161 @Override 162 public void run() { 163 clearTransitionAnimations(); 164 } 165 }); 166 } 167 }); 168 startAnimation(transitions.first()); 169 } 170 } 171 172 private void setPhoto(Photo photo) { 173 if (this.photo != null) { 174 this.photo.clear(); 175 this.photo = null; 176 } 177 this.photo = photo; 178 179 // Scale-down (if necessary) and center the photo for display. 180 displayMatrix.reset(); 181 RectF bounds = getPhotoBounds(); 182 float scale = RectUtils.getDisplayScale(bounds, this); 183 displayMatrix.setScale(scale, scale); 184 displayMatrix.mapRect(bounds); 185 RectUtils.postCenterMatrix(bounds, this, displayMatrix); 186 } 187 188 private void clearTransitionAnimations() { 189 if (transitions != null) { 190 transitions.first().setAnimationListener(null); 191 transitions.second().setAnimationListener(null); 192 transitions = null; 193 clearAnimation(); 194 } 195 } 196 197 /** 198 * Sets transition animations in the next update() to transit out the current bitmap and 199 * transit in the new replacing one. Transition animations will be cleared once done. 200 */ 201 public void setTransitionAnimations(AnimationPair transitions) { 202 clearTransitionAnimations(); 203 this.transitions = transitions; 204 } 205 } 206