content: /*
   * Copyright (C) 2007 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.onegravity.rteditor.media.crop;
  
  import android.content.Context;
  import android.graphics.Bitmap;
  import android.graphics.Matrix;
  import android.graphics.RectF;
  import android.graphics.drawable.Drawable;
  import android.os.Handler;
  import android.util.AttributeSet;
  import android.view.KeyEvent;
  
  abstract class ImageViewTouchBase extends androidx.appcompat.widget.AppCompatImageView {
  
      @SuppressWarnings("unused")
      private static final String TAG = "ImageViewTouchBase";
  
      // This is the base transformation which is used to show the image
      // initially. The current computation for this shows the image in
      // it's entirety, letterboxing as needed. One could choose to
      // show the image as cropped instead.
      //
      // This matrix is recomputed when we go from the thumbnail image to
      // the full size image.
      protected Matrix mBaseMatrix = new Matrix();
  
      // This is the supplementary transformation which reflects what
      // the user has done in terms of zooming and panning.
      //
      // This matrix remains the same when we go from the thumbnail image
      // to the full size image.
      protected Matrix mSuppMatrix = new Matrix();
  
      // This is the final matrix which is computed as the concatentation
      // of the base matrix and the supplementary matrix.
      private final Matrix mDisplayMatrix = new Matrix();
  
      // Temporary buffer used for getting the values out of a matrix.
      private final float[] mMatrixValues = new float[9];
  
      // The current bitmap being displayed.
      final protected RotateBitmap mBitmapDisplayed = new RotateBitmap(null);
  
      int mThisWidth = -1, mThisHeight = -1;
  
      float mMaxZoom;
  
      int mLeft;
  
      int mRight;
  
      int mTop;
  
      int mBottom;
  
      // ImageViewTouchBase will pass a Bitmap to the Recycler if it has finished
      // its use of that Bitmap.
      public interface Recycler {
          public void recycle(Bitmap b);
      }
  
      public void setRecycler(Recycler r) {
          mRecycler = r;
      }
  
      private Recycler mRecycler;
  
      @Override
      protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
          super.onLayout(changed, left, top, right, bottom);
          mLeft = left;
          mRight = right;
          mTop = top;
          mBottom = bottom;
          mThisWidth = right - left;
          mThisHeight = bottom - top;
          Runnable r = mOnLayoutRunnable;
          if (r != null) {
              mOnLayoutRunnable = null;
              r.run();
          }
          if (mBitmapDisplayed.getBitmap() != null) {
              getProperBaseMatrix(mBitmapDisplayed, mBaseMatrix);
              setImageMatrix(getImageViewMatrix());
          }
      }
  
      @Override
      public boolean onKeyDown(int keyCode, KeyEvent event) {
          if (keyCode == KeyEvent.KEYCODE_BACK && getScale() > 1.0f) {
              // If we're zoomed in, pressing Back jumps out to show the entire
              // image, otherwise Back returns the user to the gallery.
              zoomTo(1.0f);
              return true;
          }
          return super.onKeyDown(keyCode, event);
      }
  
      protected Handler mHandler = new Handler();
  
      @Override
      public void setImageBitmap(Bitmap bitmap) {
          setImageBitmap(bitmap, 0);
      }
  
      private void setImageBitmap(Bitmap bitmap, int rotation) {
          super.setImageBitmap(bitmap);
          Drawable d = getDrawable();
          if (d != null) {
              d.setDither(true);
          }
  
          Bitmap old = mBitmapDisplayed.getBitmap();
          mBitmapDisplayed.setBitmap(bitmap);
          mBitmapDisplayed.setRotation(rotation);
  
          if (old != null && old != bitmap && mRecycler != null) {
              mRecycler.recycle(old);
          }
      }
  
      public void clear() {
          setImageBitmapResetBase(null, true);
      }
  
      private Runnable mOnLayoutRunnable = null;
  
      // This function changes bitmap, reset base matrix according to the size
      // of the bitmap, and optionally reset the supplementary matrix.
      public void setImageBitmapResetBase(final Bitmap bitmap, final boolean resetSupp) {
          setImageRotateBitmapResetBase(new RotateBitmap(bitmap), resetSupp);
      }
  
      public void setImageRotateBitmapResetBase(final RotateBitmap bitmap, final boolean resetSupp) {
  
          final int viewWidth = getWidth();
  
          if (viewWidth <= 0) {
              mOnLayoutRunnable = new Runnable() {
                  public void run() {
  
                      setImageRotateBitmapResetBase(bitmap, resetSupp);
                  }
              };
              return;
          }
  
          if (bitmap.getBitmap() != null) {
              getProperBaseMatrix(bitmap, mBaseMatrix);
              setImageBitmap(bitmap.getBitmap(), bitmap.getRotation());
          } else {
              mBaseMatrix.reset();
              setImageBitmap(null);
          }
  
          if (resetSupp) {
              mSuppMatrix.reset();
          }
          setImageMatrix(getImageViewMatrix());
          mMaxZoom = maxZoom();
      }
  
      // Center as much as possible in one or both axis. Centering is
      // defined as follows: if the image is scaled down below the
      // view's dimensions then center it (literally). If the image
      // is scaled larger than the view and is translated out of view
      // then translate it back into view (i.e. eliminate black bars).
      protected void center(boolean horizontal, boolean vertical) {
          if (mBitmapDisplayed.getBitmap() == null) {
              return;
          }
  
          Matrix m = getImageViewMatrix();
  
          RectF rect = new RectF(0, 0, mBitmapDisplayed.getBitmap().getWidth(),
                  mBitmapDisplayed.getBitmap().getHeight());
  
          m.mapRect(rect);
  
          float height = rect.height();
          float width = rect.width();
  
          float deltaX = 0, deltaY = 0;
  
          if (vertical) {
              int viewHeight = getHeight();
              if (height < viewHeight) {
                  deltaY = (viewHeight - height) / 2 - rect.top;
              } else if (rect.top > 0) {
                  deltaY = -rect.top;
              } else if (rect.bottom < viewHeight) {
                  deltaY = getHeight() - rect.bottom;
              }
          }
  
          if (horizontal) {
              int viewWidth = getWidth();
              if (width < viewWidth) {
                  deltaX = (viewWidth - width) / 2 - rect.left;
              } else if (rect.left > 0) {
                  deltaX = -rect.left;
              } else if (rect.right < viewWidth) {
                  deltaX = viewWidth - rect.right;
              }
          }
  
          postTranslate(deltaX, deltaY);
          setImageMatrix(getImageViewMatrix());
      }
  
      public ImageViewTouchBase(Context context) {
          super(context);
          init();
      }
  
      public ImageViewTouchBase(Context context, AttributeSet attrs) {
          super(context, attrs);
          init();
      }
  
      private void init() {
          setScaleType(ScaleType.MATRIX);
      }
  
      protected float getValue(Matrix matrix, int whichValue) {
          matrix.getValues(mMatrixValues);
          return mMatrixValues[whichValue];
      }
  
      // Get the scale factor out of the matrix.
      protected float getScale(Matrix matrix) {
          return getValue(matrix, Matrix.MSCALE_X);
      }
  
      protected float getScale() {
          return getScale(mSuppMatrix);
      }
  
      // Setup the base matrix so that the image is centered and scaled properly.
      private void getProperBaseMatrix(RotateBitmap bitmap, Matrix matrix) {
          float viewWidth = getWidth();
          float viewHeight = getHeight();
  
          float w = bitmap.getWidth();
          float h = bitmap.getHeight();
          matrix.reset();
  
          // We limit up-scaling to 2x otherwise the result may look bad if it's
          // a small icon.
          float widthScale = Math.min(viewWidth / w, 2.0f);
          float heightScale = Math.min(viewHeight / h, 2.0f);
          float scale = Math.min(widthScale, heightScale);
  
          matrix.postConcat(bitmap.getRotateMatrix());
          matrix.postScale(scale, scale);
  
          matrix.postTranslate((viewWidth - w * scale) / 2F, (viewHeight - h * scale) / 2F);
      }
  
      // Combine the base matrix and the supp matrix to make the final matrix.
      protected Matrix getImageViewMatrix() {
          // The final matrix is computed as the concatentation of the base matrix
          // and the supplementary matrix.
          mDisplayMatrix.set(mBaseMatrix);
          mDisplayMatrix.postConcat(mSuppMatrix);
          return mDisplayMatrix;
      }
  
      static final float SCALE_RATE = 1.25F;
  
      // Sets the maximum zoom, which is a scale relative to the base matrix. It
      // is calculated to show the image at 400% zoom regardless of screen or
      // image orientation. If in the future we decode the full 3 megapixel image,
      // rather than the current 1024x768, this should be changed down to 200%.
      protected float maxZoom() {
          if (mBitmapDisplayed.getBitmap() == null) {
              return 1F;
          }
  
          float fw = (float) mBitmapDisplayed.getWidth() / (float) mThisWidth;
          float fh = (float) mBitmapDisplayed.getHeight() / (float) mThisHeight;
          float max = Math.max(fw, fh) * 4;
          return max;
      }
  
      protected void zoomTo(float scale, float centerX, float centerY) {
          if (scale > mMaxZoom) {
              scale = mMaxZoom;
          }
  
          float oldScale = getScale();
          float deltaScale = scale / oldScale;
  
          mSuppMatrix.postScale(deltaScale, deltaScale, centerX, centerY);
          setImageMatrix(getImageViewMatrix());
          center(true, true);
      }
  
      protected void zoomTo(final float scale, final float centerX, final float centerY, final float durationMs) {
  
          final float incrementPerMs = (scale - getScale()) / durationMs;
          final float oldScale = getScale();
          final long startTime = System.currentTimeMillis();
  
          mHandler.post(new Runnable() {
              public void run() {
  
                  long now = System.currentTimeMillis();
                  float currentMs = Math.min(durationMs, now - startTime);
                  float target = oldScale + (incrementPerMs * currentMs);
                  zoomTo(target, centerX, centerY);
  
                  if (currentMs < durationMs) {
                      mHandler.post(this);
                  }
              }
          });
      }
  
      protected void zoomTo(float scale) {
          float cx = getWidth() / 2F;
          float cy = getHeight() / 2F;
  
          zoomTo(scale, cx, cy);
      }
  
      protected void zoomIn() {
          zoomIn(SCALE_RATE);
      }
  
      protected void zoomOut() {
          zoomOut(SCALE_RATE);
      }
  
      protected void zoomIn(float rate) {
          if (getScale() >= mMaxZoom) {
              return; // Don't let the user zoom into the molecular level.
          }
          if (mBitmapDisplayed.getBitmap() == null) {
              return;
          }
  
          float cx = getWidth() / 2F;
          float cy = getHeight() / 2F;
  
          mSuppMatrix.postScale(rate, rate, cx, cy);
          setImageMatrix(getImageViewMatrix());
      }
  
      protected void zoomOut(float rate) {
          if (mBitmapDisplayed.getBitmap() == null) {
              return;
          }
  
          float cx = getWidth() / 2F;
          float cy = getHeight() / 2F;
  
          // Zoom out to at most 1x.
          Matrix tmp = new Matrix(mSuppMatrix);
          tmp.postScale(1F / rate, 1F / rate, cx, cy);
  
          if (getScale(tmp) < 1F) {
              mSuppMatrix.setScale(1F, 1F, cx, cy);
          } else {
              mSuppMatrix.postScale(1F / rate, 1F / rate, cx, cy);
          }
          setImageMatrix(getImageViewMatrix());
          center(true, true);
      }
  
      protected void postTranslate(float dx, float dy) {
          mSuppMatrix.postTranslate(dx, dy);
      }
  
      protected void panBy(float dx, float dy) {
          postTranslate(dx, dy);
          setImageMatrix(getImageViewMatrix());
      }
  }