title: Correctly setting a focus point for image scalling
body: <p>I have a <code>SurfaceView</code> that is resposible for drawing a <code>Bitmap</code> as a background and another one that will be used as an overlay. So I've decided to do all transformations using a <code>Matrix</code> that can be used for both bitmaps as it is (I think) one of the fastest ways to do it without using OpenGL.</p>
  
  <p>I've been able to implement panning around and zooming but I have some problems with what I've came with: </p>
  
  <ul>
  <li>I wasn't able to find a way how to focus on the center of the two
  fingers while zooming, the image always resets to its initial state
  (that is, without panning nor scalling) before the new scale being
  applied. Besides looking wrong, that doesn't allow the user to zoom
  out to see the whole image and then zoom in on the part that is
  important. </li>
  <li>After the scalling operation the image won't be at the
  same place after the new draw pass because the translation value will
  be different.</li>
  </ul>
  
  <p>Is there a way to achieve that using a Matrix or is there another solution?</p>
  
  <p>Code is below (I use a <code>SurfaceHolder</code> in a separate thread do lock the SurfaceView canvas and call its <code>doDraw</code> method):</p>
  
  <pre><code>public class MapSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
  
      public void doDraw(Canvas canvas) {
          canvas.drawColor(Color.BLACK);
          canvas.drawBitmap(mBitmap, mTransformationMatrix, mPaintAA);
      }
  
      @Override
      public boolean onTouchEvent(MotionEvent event) {
          switch (event.getAction() &amp; MotionEvent.ACTION_MASK) {
              case MotionEvent.ACTION_POINTER_DOWN: {
                  if (event.getPointerCount() == 2) {
                      mOriginalDistance = MathUtils.distanceBetween(event.getX(0), event.getX(1), event.getY(0), event.getY(1));
                      mScreenMidpoint = MathUtils.midpoint(event.getX(0), event.getX(1), event.getY(0), event.getY(1));
                      mImageMidpoint = MathUtils.midpoint((mXPosition+event.getX(0))/mScale, (mXPosition+event.getX(1))/mScale, (mYPosition+event.getY(0))/mScale, (mYPosition+event.getY(1))/mScale);
                      mOriginalScale = mScale;
                  }
              }
              case MotionEvent.ACTION_DOWN: {
                  mOriginalTouchPoint = new Point((int)event.getX(), (int)event.getY());
                  mOriginalPosition = new Point(mXPosition, mYPosition);
                  break;
              }
              case MotionEvent.ACTION_MOVE: {
                  if (event.getPointerCount() == 2) {
                      final double currentDistance = MathUtils.distanceBetween(event.getX(0), event.getX(1), event.getY(0), event.getY(1));
                      if (mIsZooming || currentDistance - mOriginalDistance &gt; mPinchToZoomTolerance || mOriginalDistance - currentDistance &gt; mPinchToZoomTolerance) {
                          final float distanceRatio = (float) (currentDistance / mOriginalDistance);
                          float tempZoom = mOriginalScale * distanceRatio;
  
                          mScale = Math.min(10, Math.max(Math.min((float)getHeight()/(float)mBitmap.getHeight(), (float)getWidth()/(float)mBitmap.getWidth()), tempZoom));
                          mScale = (float) MathUtils.roundToDecimals(mScale, 1);
                          mIsZooming = true;
                          mTransformationMatrix = new Matrix();
                          mTransformationMatrix.setScale(mScale, mScale);//, mImageMidpoint.x, mImageMidpoint.y);
                      } else {
                          System.out.println("Dragging");
                          mIsZooming = false;
                          final int deltaX = (int) ((int) (mOriginalTouchPoint.x - event.getX()));
                          final int deltaY = (int) ((int) (mOriginalTouchPoint.y - event.getY()));
                          mXPosition = mOriginalPosition.x + deltaX;  
                          mYPosition = mOriginalPosition.y + deltaY;
                          validatePositions();
                          mTransformationMatrix = new Matrix();
                          mTransformationMatrix.setScale(mScale, mScale);
                          mTransformationMatrix.postTranslate(-mXPosition, -mYPosition);
                      }
                  }
                  break;
              }
              case MotionEvent.ACTION_UP:
              case MotionEvent.ACTION_POINTER_UP: {
                  mIsZooming = false;
                  validatePositions();
                  mTransformationMatrix = new Matrix();
                  mTransformationMatrix.setScale(mScale, mScale);
                  mTransformationMatrix.postTranslate(-mXPosition, -mYPosition);
              }
          }
          return true;
      }
  
      private void validatePositions() {
          // Lower right corner
          mXPosition = Math.min(mXPosition, (int)((mBitmap.getWidth() * mScale)-getWidth()));
          mYPosition = Math.min(mYPosition, (int)((mBitmap.getHeight() * mScale)-getHeight()));
          // Upper left corner
          mXPosition = Math.max(mXPosition, 0);
          mYPosition = Math.max(mYPosition, 0);
          // Image smaller than the container, should center it
          if (mBitmap.getWidth() * mScale &lt;= getWidth()) {
              mXPosition = (int) -((getWidth() - (mBitmap.getWidth() * mScale))/2);
          }
          if (mBitmap.getHeight() * mScale &lt;= getHeight()) { 
              mYPosition = (int) -((getHeight() - (mBitmap.getHeight() * mScale))/2);
          }
      }
  }
  </code></pre>