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.gallery3d.photoeditor.actions; 18 19 import android.content.Context; 20 import android.graphics.Bitmap; 21 import android.graphics.Canvas; 22 import android.graphics.Color; 23 import android.graphics.Matrix; 24 import android.graphics.Paint; 25 import android.graphics.Path; 26 import android.graphics.PointF; 27 import android.graphics.RectF; 28 import android.util.AttributeSet; 29 import android.view.MotionEvent; 30 31 /** 32 * A view that tracks touch motions as paths and paints them as doodles. 33 */ 34 class DoodleView extends FullscreenToolView { 35 36 /** 37 * Listener of doodle paths. 38 */ 39 public interface OnDoodleChangeListener { 40 41 void onDoodleInPhotoBounds(); 42 43 void onDoodleFinished(Path path, int color); 44 } 45 46 private final Path normalizedPath = new Path(); 47 private final Path drawingPath = new Path(); 48 private final Paint doodlePaint = new DoodlePaint(); 49 private final Paint bitmapPaint = new Paint(Paint.DITHER_FLAG); 50 private final PointF lastPoint = new PointF(); 51 private final Matrix pathMatrix = new Matrix(); 52 private final Matrix displayMatrix = new Matrix(); 53 54 private Bitmap bitmap; 55 private Canvas bitmapCanvas; 56 private OnDoodleChangeListener listener; 57 58 public DoodleView(Context context, AttributeSet attrs) { 59 super(context, attrs); 60 } 61 62 public void setOnDoodleChangeListener(OnDoodleChangeListener listener) { 63 this.listener = listener; 64 } 65 66 @Override 67 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 68 super.onSizeChanged(w, h, oldw, oldh); 69 70 RectF r = new RectF(0, 0, getPhotoWidth(), getPhotoHeight()); 71 if ((bitmap == null) && !r.isEmpty()) { 72 bitmap = Bitmap.createBitmap((int) r.width(), (int) r.height(), 73 Bitmap.Config.ARGB_8888); 74 bitmap.eraseColor(0x00000000); 75 bitmapCanvas = new Canvas(bitmap); 76 77 // Set up a matrix that maps back normalized paths to be drawn on the bitmap or canvas. 78 pathMatrix.setRectToRect(new RectF(0, 0, 1, 1), r, Matrix.ScaleToFit.FILL); 79 } 80 displayMatrix.setRectToRect(r, displayBounds, Matrix.ScaleToFit.FILL); 81 } 82 83 private void drawDoodle(Canvas canvas) { 84 if ((canvas != null) && !normalizedPath.isEmpty()) { 85 drawingPath.set(normalizedPath); 86 drawingPath.transform(pathMatrix); 87 canvas.drawPath(drawingPath, doodlePaint); 88 } 89 } 90 91 public void setColor(int color) { 92 // Reset path to draw in a new color. 93 finishCurrentPath(); 94 normalizedPath.moveTo(lastPoint.x, lastPoint.y); 95 doodlePaint.setColor(Color.argb(192, Color.red(color), Color.green(color), 96 Color.blue(color))); 97 } 98 99 private void finishCurrentPath() { 100 if (!normalizedPath.isEmpty()) { 101 // Update the finished path to the bitmap. 102 drawDoodle(bitmapCanvas); 103 if (listener != null) { 104 listener.onDoodleFinished(new Path(normalizedPath), doodlePaint.getColor()); 105 } 106 normalizedPath.rewind(); 107 invalidate(); 108 } 109 } 110 111 private void checkCurrentPathInBounds() { 112 if ((listener != null) && !normalizedPath.isEmpty()) { 113 RectF r = new RectF(); 114 normalizedPath.computeBounds(r, false); 115 if (r.intersects(0, 0, 1, 1)) { 116 listener.onDoodleInPhotoBounds(); 117 } 118 } 119 } 120 121 @Override 122 public boolean onTouchEvent(MotionEvent event) { 123 super.onTouchEvent(event); 124 125 if (isEnabled()) { 126 float x = event.getX(); 127 float y = event.getY(); 128 129 switch (event.getAction()) { 130 case MotionEvent.ACTION_DOWN: 131 mapPhotoPoint(x, y, lastPoint); 132 normalizedPath.moveTo(lastPoint.x, lastPoint.y); 133 break; 134 135 case MotionEvent.ACTION_MOVE: 136 float lastX = lastPoint.x; 137 float lastY = lastPoint.y; 138 mapPhotoPoint(x, y, lastPoint); 139 normalizedPath.quadTo(lastX, lastY, (lastX + lastPoint.x) / 2, 140 (lastY + lastPoint.y) / 2); 141 checkCurrentPathInBounds(); 142 invalidate(); 143 break; 144 145 case MotionEvent.ACTION_CANCEL: 146 case MotionEvent.ACTION_UP: 147 // Line to last position with offset to draw at least dots for single clicks. 148 mapPhotoPoint(x + 1, y + 1, lastPoint); 149 normalizedPath.lineTo(lastPoint.x, lastPoint.y); 150 checkCurrentPathInBounds(); 151 finishCurrentPath(); 152 break; 153 } 154 } 155 return true; 156 } 157 158 @Override 159 protected void onDraw(Canvas canvas) { 160 super.onDraw(canvas); 161 162 canvas.save(); 163 canvas.clipRect(displayBounds); 164 canvas.concat(displayMatrix); 165 if (bitmap != null) { 166 canvas.drawBitmap(bitmap, 0, 0, bitmapPaint); 167 } 168 drawDoodle(canvas); 169 canvas.restore(); 170 } 171 } 172