content: /*
   * Copyright (C) 2010 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.android.gallery3d.common;
  
  import android.graphics.Bitmap;
  import android.graphics.Bitmap.CompressFormat;
  import android.graphics.BitmapFactory;
  import android.graphics.Canvas;
  import android.graphics.Matrix;
  import android.graphics.Paint;
  import android.os.Build;
  import android.util.FloatMath;
  import android.util.Log;
  
  import java.io.ByteArrayOutputStream;
  import java.lang.reflect.InvocationTargetException;
  import java.lang.reflect.Method;
  
  public class BitmapUtils {
      private static final String TAG = "BitmapUtils";
      private static final int DEFAULT_JPEG_QUALITY = 90;
      public static final int UNCONSTRAINED = -1;
  
      private BitmapUtils(){}
  
      /*
       * Compute the sample size as a function of minSideLength
       * and maxNumOfPixels.
       * minSideLength is used to specify that minimal width or height of a
       * bitmap.
       * maxNumOfPixels is used to specify the maximal size in pixels that is
       * tolerable in terms of memory usage.
       *
       * The function returns a sample size based on the constraints.
       * Both size and minSideLength can be passed in as UNCONSTRAINED,
       * which indicates no care of the corresponding constraint.
       * The functions prefers returning a sample size that
       * generates a smaller bitmap, unless minSideLength = UNCONSTRAINED.
       *
       * Also, the function rounds up the sample size to a power of 2 or multiple
       * of 8 because BitmapFactory only honors sample size this way.
       * For example, BitmapFactory downsamples an image by 2 even though the
       * request is 3. So we round up the sample size to avoid OOM.
       */
      public static int computeSampleSize(int width, int height,
              int minSideLength, int maxNumOfPixels) {
          int initialSize = computeInitialSampleSize(
                  width, height, minSideLength, maxNumOfPixels);
  
          return initialSize <= 8
                  ? Utils.nextPowerOf2(initialSize)
                  : (initialSize + 7) / 8 * 8;
      }
  
      private static int computeInitialSampleSize(int w, int h,
              int minSideLength, int maxNumOfPixels) {
          if (maxNumOfPixels == UNCONSTRAINED
                  && minSideLength == UNCONSTRAINED) return 1;
  
          int lowerBound = (maxNumOfPixels == UNCONSTRAINED) ? 1 :
                  (int) FloatMath.ceil(FloatMath.sqrt((float) (w * h) / maxNumOfPixels));
  
          if (minSideLength == UNCONSTRAINED) {
              return lowerBound;
          } else {
              int sampleSize = Math.min(w / minSideLength, h / minSideLength);
              return Math.max(sampleSize, lowerBound);
          }
      }
  
      // This computes a sample size which makes the longer side at least
      // minSideLength long. If that's not possible, return 1.
      public static int computeSampleSizeLarger(int w, int h,
              int minSideLength) {
          int initialSize = Math.max(w / minSideLength, h / minSideLength);
          if (initialSize <= 1) return 1;
  
          return initialSize <= 8
                  ? Utils.prevPowerOf2(initialSize)
                  : initialSize / 8 * 8;
      }
  
      // Find the min x that 1 / x >= scale
      public static int computeSampleSizeLarger(float scale) {
          int initialSize = (int) FloatMath.floor(1f / scale);
          if (initialSize <= 1) return 1;
  
          return initialSize <= 8
                  ? Utils.prevPowerOf2(initialSize)
                  : initialSize / 8 * 8;
      }
  
      // Find the max x that 1 / x <= scale.
      public static int computeSampleSize(float scale) {
          Utils.assertTrue(scale > 0);
          int initialSize = Math.max(1, (int) FloatMath.ceil(1 / scale));
          return initialSize <= 8
                  ? Utils.nextPowerOf2(initialSize)
                  : (initialSize + 7) / 8 * 8;
      }
  
      public static Bitmap resizeBitmapByScale(
              Bitmap bitmap, float scale, boolean recycle) {
          int width = Math.round(bitmap.getWidth() * scale);
          int height = Math.round(bitmap.getHeight() * scale);
          if (width == bitmap.getWidth()
                  && height == bitmap.getHeight()) return bitmap;
          Bitmap target = Bitmap.createBitmap(width, height, getConfig(bitmap));
          Canvas canvas = new Canvas(target);
          canvas.scale(scale, scale);
          Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG);
          canvas.drawBitmap(bitmap, 0, 0, paint);
          if (recycle) bitmap.recycle();
          return target;
      }
  
      private static Bitmap.Config getConfig(Bitmap bitmap) {
          Bitmap.Config config = bitmap.getConfig();
          if (config == null) {
              config = Bitmap.Config.ARGB_8888;
          }
          return config;
      }
  
      public static Bitmap resizeDownBySideLength(
              Bitmap bitmap, int maxLength, boolean recycle) {
          int srcWidth = bitmap.getWidth();
          int srcHeight = bitmap.getHeight();
          float scale = Math.min(
                  (float) maxLength / srcWidth, (float) maxLength / srcHeight);
          if (scale >= 1.0f) return bitmap;
          return resizeBitmapByScale(bitmap, scale, recycle);
      }
  
      public static Bitmap resizeAndCropCenter(Bitmap bitmap, int size, boolean recycle) {
          int w = bitmap.getWidth();
          int h = bitmap.getHeight();
          if (w == size && h == size) return bitmap;
  
          // scale the image so that the shorter side equals to the target;
          // the longer side will be center-cropped.
          float scale = (float) size / Math.min(w,  h);
  
          Bitmap target = Bitmap.createBitmap(size, size, getConfig(bitmap));
          int width = Math.round(scale * bitmap.getWidth());
          int height = Math.round(scale * bitmap.getHeight());
          Canvas canvas = new Canvas(target);
          canvas.translate((size - width) / 2f, (size - height) / 2f);
          canvas.scale(scale, scale);
          Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG);
          canvas.drawBitmap(bitmap, 0, 0, paint);
          if (recycle) bitmap.recycle();
          return target;
      }
  
      public static void recycleSilently(Bitmap bitmap) {
          if (bitmap == null) return;
          try {
              bitmap.recycle();
          } catch (Throwable t) {
              Log.w(TAG, "unable recycle bitmap", t);
          }
      }
  
      public static Bitmap rotateBitmap(Bitmap source, int rotation, boolean recycle) {
          if (rotation == 0) return source;
          int w = source.getWidth();
          int h = source.getHeight();
          Matrix m = new Matrix();
          m.postRotate(rotation);
          Bitmap bitmap = Bitmap.createBitmap(source, 0, 0, w, h, m, true);
          if (recycle) source.recycle();
          return bitmap;
      }
  
      public static Bitmap createVideoThumbnail(String filePath) {
          // MediaMetadataRetriever is available on API Level 8
          // but is hidden until API Level 10
          Class<?> clazz = null;
          Object instance = null;
          try {
              clazz = Class.forName("android.media.MediaMetadataRetriever");
              instance = clazz.newInstance();
  
              Method method = clazz.getMethod("setDataSource", String.class);
              method.invoke(instance, filePath);
  
              // The method name changes between API Level 9 and 10.
              if (Build.VERSION.SDK_INT <= 9) {
                  return (Bitmap) clazz.getMethod("captureFrame").invoke(instance);
              } else {
                  byte[] data = (byte[]) clazz.getMethod("getEmbeddedPicture").invoke(instance);
                  if (data != null) {
                      Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
                      if (bitmap != null) return bitmap;
                  }
                  return (Bitmap) clazz.getMethod("getFrameAtTime").invoke(instance);
              }
          } catch (IllegalArgumentException ex) {
              // Assume this is a corrupt video file
          } catch (RuntimeException ex) {
              // Assume this is a corrupt video file.
          } catch (InstantiationException e) {
              Log.e(TAG, "createVideoThumbnail", e);
          } catch (InvocationTargetException e) {
              Log.e(TAG, "createVideoThumbnail", e);
          } catch (ClassNotFoundException e) {
              Log.e(TAG, "createVideoThumbnail", e);
          } catch (NoSuchMethodException e) {
              Log.e(TAG, "createVideoThumbnail", e);
          } catch (IllegalAccessException e) {
              Log.e(TAG, "createVideoThumbnail", e);
          } finally {
              try {
                  if (instance != null) {
                      clazz.getMethod("release").invoke(instance);
                  }
              } catch (Exception ignored) {
              }
          }
          return null;
      }
  
      public static byte[] compressToBytes(Bitmap bitmap) {
          return compressToBytes(bitmap, DEFAULT_JPEG_QUALITY);
      }
  
      public static byte[] compressToBytes(Bitmap bitmap, int quality) {
          ByteArrayOutputStream baos = new ByteArrayOutputStream(65536);
          bitmap.compress(CompressFormat.JPEG, quality, baos);
          return baos.toByteArray();
      }
  
      public static boolean isSupportedByRegionDecoder(String mimeType) {
          if (mimeType == null) return false;
          mimeType = mimeType.toLowerCase();
          return mimeType.startsWith("image/") &&
                  (!mimeType.equals("image/gif") && !mimeType.endsWith("bmp"));
      }
  
      public static boolean isRotationSupported(String mimeType) {
          if (mimeType == null) return false;
          mimeType = mimeType.toLowerCase();
          return mimeType.equals("image/jpeg");
      }
  }
  