Translating and scaling a canvas, then finding bitmaps on touch!

  • Replies:2
laurence saleh
  • Forum posts: 4

Apr 30, 2013, 11:23:58 AM via Website

So I have a canvas that I draw a bitmap onto.. When the scalefactor of the canvas is 1.0f everything works perfectly how I want it to.. However the problem is when I change the scale of the canvas. Either zoomed in or out I run into problems with the coordinates and finding the bitmap so I can move it on touch..

I've tried dividing coordinates by the scalefactor.. multiplying.. I just can't work out the right calculation to get my desired result! Please help!

Here is my code

1public void onDraw(Canvas canvas) {
2 // Log.v(TAG, "onDraw()");
3 clipBounds = canvas.getClipBounds();
4 if (bitmap == null) {
5 Log.w(TAG, "nothing to draw - bitmap is null");
6 super.onDraw(canvas);
7 return;
8 }
9
10 if (firstDraw && (bitmap.getHeight() > 0) && (bitmap.getWidth() > 0)) {
11 // Don't let the user zoom out so much that the image is smaller
12 // than its containing frame
13 float minXScaleFactor = (float) viewWidth
14 / (float) bitmap.getWidth();
15 float minYScaleFactor = (float) viewHeight
16 / (float) bitmap.getHeight();
17 minScaleFactor = Math.max(minXScaleFactor, minYScaleFactor);
18 Log.d(TAG, "minScaleFactor: " + minScaleFactor);
19 // mScaleFactor = minScaleFactor; //start out "zoomed out" all the
20 // way
21
22 mPosX = mPosY = 0;
23 firstDraw = false;
24 }
25 mScaleFactor = Math.max(mScaleFactor, minScaleFactor);
26
27 canvasHeight = canvas.getHeight();
28 canvasWidth = canvas.getWidth();
29 // Log.d(TAG, "canvas density: " + canvas.getDensity() +
30 // " bitmap density: " + bitmap.getDensity());
31
32 // Log.d(TAG, "mScaleFactor: " + mScaleFactor);
33
34 // Save the canvas without translating (panning) or scaling (zooming)
35 // After each change, restore to this state, instead of compounding
36 // changes upon changes
37 canvas.save();
38 int maxX, minX, maxY, minY;
39 // Regardless of the screen density (HDPI, MDPI) or the scale factor,
40 // The image always consists of bitmap width divided by 2 pixels. If an
41 // image
42 // is 200 pixels wide and you scroll right 100 pixels, you just scrolled
43 // the image
44 // off the screen to the left.
45 minX = (int) (((viewWidth / mScaleFactor) - bitmap.getWidth()) / 2);
46 maxX = 0;
47 // How far can we move the image vertically without having a gap between
48 // image and frame?
49 minY = (int) (((viewHeight / mScaleFactor) - bitmap.getHeight()) / 2);
50 maxY = 0;
51 // Log.d(TAG, "minX: " + minX + " maxX: " + maxX + " minY: " + minY
52 // + " maxY: " + maxY);
53 // Do not go beyond the boundaries of the image
54 if (mPosX > maxX) {
55 mPosX = maxX;
56 }
57 if (mPosX < minX) {
58 mPosX = minX;
59 }
60 if (mPosY > maxY) {
61 mPosY = maxY;
62 }
63 if (mPosY < minY) {
64 mPosY = minY;
65 }
66
67 canvas.scale(mScaleFactor, mScaleFactor);
68 // canvas.scale(0.6f, 0.6f);
69 // Log.i("","mPos " + mPosX);
70 canvas.translate(mPosX, mPosY);
71
72 canvas.drawBitmap(bitmap, mPosX, mPosY, null);
73 canvas.drawBitmap(bmp, bPosX + mPosX, bPosY + mPosY, null);
74
75 if (!firstDraw) {
76 bitmapCanvas.drawBitmap(bitmap, mPosX, mPosY, null);
77 bitmapCanvas.drawBitmap(bmp, bPosX + mPosX, bPosY + mPosY, null);
78 }
79
80 super.onDraw(canvas);
81
82 canvas.restore(); // clear translation/scaling
83}
84
85@Override
86public boolean onTouchEvent(MotionEvent ev) {
87 // Let the ScaleGestureDetector inspect all events.
88 if (zoomEnabled) {
89 mScaleDetector.onTouchEvent(ev);
90 }
91
92 if (panEnabled) {
93 final int action = ev.getAction();
94 switch (action & MotionEvent.ACTION_MASK) {
95
96 case MotionEvent.ACTION_DOWN: {
97 final float x = ev.getX();
98 final float y = ev.getY();
99
100 mLastTouchX = x;
101 mLastTouchY = y;
102
103 mActivePointerId = ev.getPointerId(0);
104
105 int mx = (int) mPosX * -1;
106 int my = (int) mPosY * -1;
107
108 // int checkPointX = (int) (x + mx * 2);
109 // int checkPointY = (int) (y + my * 2);
110
111 int checkPointX = (int) (x + mx * 2);
112 int checkPointY = (int) (y + my * 2);
113
114 Log.i("ZOOMS", "ScaleFactor " + mScaleFactor);
115 Log.i("TOUCH COORD", "x " + checkPointX + " y " + checkPointY);
116 Log.i("BMP POS", "x " + bPosX + " bw " + (bPosX + bmp.getWidth()) + " y " + bPosY + " yw " + (bPosY + bmp.getHeight()));
117
118 if (checkPointX > bPosX
119 && checkPointX <= bPosX + bmp.getWidth()
120 && checkPointY >= bPosY
121 && checkPointY <= bPosY + bmp.getHeight())
122 bitmapFound = true;
123 else
124 bitmapFound = false;
125
126 // if(mx > bmp.)
127 break;
128 }
129
130 case MotionEvent.ACTION_MOVE: {
131 final int pointerIndex = ev.findPointerIndex(mActivePointerId);
132 final float x = ev.getX(pointerIndex);
133 final float y = ev.getY(pointerIndex);
134
135 // Only move if the ScaleGestureDetector isn't processing a
136 // gesture.
137 if (!mScaleDetector.isInProgress()) {
138 if (bitmapFound) {
139 int mx = (int) mPosX * -1;
140 int my = (int) mPosY * -1;
141
142 int checkPointX = (int) (x + mx * 2);
143 int checkPointY = (int) (y + my * 2);
144
145 moveBitmap(checkPointX, checkPointY);
146 } else
147 moveCanvas(x, y);
148
149 invalidate();
150 }
151 mLastTouchX = x;
152 mLastTouchY = y;
153
154 break;
155 }
156
157 case MotionEvent.ACTION_UP: {
158 mActivePointerId = INVALID_POINTER_ID;
159 break;
160 }
161
162 case MotionEvent.ACTION_CANCEL: {
163 mActivePointerId = INVALID_POINTER_ID;
164 break;
165 }
166
167 case MotionEvent.ACTION_POINTER_UP: {
168 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
169 final int pointerId = ev.getPointerId(pointerIndex);
170 if (pointerId == mActivePointerId) {
171 // This was our active pointer going up. Choose a new
172 // active pointer and adjust accordingly.
173 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
174 mLastTouchX = ev.getX(newPointerIndex);
175 mLastTouchY = ev.getY(newPointerIndex);
176 mActivePointerId = ev.getPointerId(newPointerIndex);
177
178 }
179 break;
180 }
181 }
182 }
183 return true;
184}
185
186public void moveBitmap(float x, float y) {
187
188 // Adjust for zoom factor. Otherwise, the user's finger moving 10 pixels
189 // at 200% zoom causes the image to slide 20 pixels instead of perfectly
190 // following the user's touch
191
192 x = x / mScaleFactor;
193 y = y / mScaleFactor;
194
195 bPosX = x;
196 bPosY = y;
197
198}
199
200public void moveCanvas(float x, float y) {
201 float dx = x - mLastTouchX;
202 float dy = y - mLastTouchY;
203
204 // Adjust for zoom factor. Otherwise, the user's finger moving 10 pixels
205 // at 200% zoom causes the image to slide 20 pixels instead of perfectly
206 // following the user's touch
207 dx /= (mScaleFactor * 2);
208 dy /= (mScaleFactor * 2);
209
210 mPosX += dx;
211 mPosY += dy;
212
213 // Log.v(TAG, "moving by " + dx + "," + dy + " mScaleFactor: " +
214 // mScaleFactor);
215}
216
217private class ScaleListener extends
218 ScaleGestureDetector.SimpleOnScaleGestureListener {
219 @Override
220 public boolean onScale(ScaleGestureDetector detector) {
221 mScaleFactor *= detector.getScaleFactor();
222 // Don't let the object get too small or too large.
223 mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));
224 // Log.d(TAG, "detector scale factor: " + detector.getScaleFactor()
225 // + " mscalefactor: " + mScaleFactor);
226
227 invalidate();
228 return true;
229 }
230}

Reply
laurence saleh
  • Forum posts: 4

May 3, 2013, 9:25:09 AM via Website

so many people viewing !!

Is there something I can explain differently? Present the source code in a different way!?

Reply
jm white
  • Forum posts: 1

Jul 23, 2017, 7:57:44 PM via Website

The solution is simple (although maybe not obvious). You should have two bitmaps in memory scaled exactly the same in onDraw. One bitmap is to be drawn to the canvas of the device and visible to the user on screen. The other image is a multi-color mask that acts as an image map. Then in the onTouch you get the event x and y coordinates, which are use in a GetPixel call to get the color of the pixel at the x, y location of the mask bitmap in memory. So, for example, you have 3 areas that you want something to happen in. These 3 areas could be colored red, green, and blue, with a white background. Then after the GetPixel call retieves the color of pixel on the mask bitmap, some switch case code decides what to do for each RGB color,with white perhaps doing nothing (being ignored with just a break out of the switch processing). Yes, it's not very memory efficient, but it works to turn a canvas into a set of "virtual" controls scaleable in a resolution independent way to any android device screen size or any pixel density--and you've got around 16.7 mi!lion possible 24 bit colors to work with. The only tricky part is the scaling to fit REAL (actual) device height/widths that are somewhat unknowable beforehand ..and then preplanning (as well as debugging) the compositing and scaling out of sight (in memory).

Reply