TodayHu 发表于 2013-1-19 04:07:34

Android缩略图类源代码

Android 2.2开始新增的缩略图类ThumbnailUtils的主要方法是静态的,对于Android 2.2或API Level8以下的工程可以直接使用,本类相对于我们常规的缩略图类考虑更周全,除了尺寸比例优化外,针对OOM的内存管理方面有更周全的处理方式,完整代码如下,


public class ThumbnailUtils {
    private static final String TAG = "ThumbnailUtils";

    /* Maximum pixels size for created bitmap. */
    private static final int MAX_NUM_PIXELS_THUMBNAIL = 512 * 384;
    private static final int MAX_NUM_PIXELS_MICRO_THUMBNAIL = 128 * 128;
    private static final int UNCONSTRAINED = -1;

    /* Options used internally. */
    private static final int OPTIONS_NONE = 0x0;
    private static final int OPTIONS_SCALE_UP = 0x1;

    /**
   * Constant used to indicate we should recycle the input in
   * {@link #extractThumbnail(Bitmap, int, int, int)} unless the output is the input.
   */
    public static final int OPTIONS_RECYCLE_INPUT = 0x2;

    /**
   * Constant used to indicate the dimension of mini thumbnail.
   * @hide Only used by media framework and media provider internally.
   */
    public static final int TARGET_SIZE_MINI_THUMBNAIL = 320;

    /**
   * Constant used to indicate the dimension of micro thumbnail.
   * @hide Only used by media framework and media provider internally.
   */
    public static final int TARGET_SIZE_MICRO_THUMBNAIL = 96;

    /**
   * This method first examines if the thumbnail embedded in EXIF is bigger than our target
   * size. If not, then it'll create a thumbnail from original image. Due to efficiency
   * consideration, we want to let MediaThumbRequest avoid calling this method twice for
   * both kinds, so it only requests for MICRO_KIND and set saveImage to true.
   *
   * This method always returns a "square thumbnail" for MICRO_KIND thumbnail.
   *
   * @param filePath the path of image file
   * @param kind could be MINI_KIND or MICRO_KIND
   * @return Bitmap
   *
   * @hide This method is only used by media framework and media provider internally.
   */
    public static Bitmap createImageThumbnail(String filePath, int kind) {
      boolean wantMini = (kind == Images.Thumbnails.MINI_KIND);
      int targetSize = wantMini
                ? TARGET_SIZE_MINI_THUMBNAIL
                : TARGET_SIZE_MICRO_THUMBNAIL;
      int maxPixels = wantMini
                ? MAX_NUM_PIXELS_THUMBNAIL
                : MAX_NUM_PIXELS_MICRO_THUMBNAIL;
      SizedThumbnailBitmap sizedThumbnailBitmap = new SizedThumbnailBitmap();
      Bitmap bitmap = null;
      MediaFileType fileType = MediaFile.getFileType(filePath);
      if (fileType != null && fileType.fileType == MediaFile.FILE_TYPE_JPEG) {
            createThumbnailFromEXIF(filePath, targetSize, maxPixels, sizedThumbnailBitmap);
            bitmap = sizedThumbnailBitmap.mBitmap;
      }

      if (bitmap == null) {
            try {
                FileDescriptor fd = new FileInputStream(filePath).getFD();
                BitmapFactory.Options options = new BitmapFactory.Options();
                options.inSampleSize = 1;
                options.inJustDecodeBounds = true;
                BitmapFactory.decodeFileDescriptor(fd, null, options);
                if (options.mCancel || options.outWidth == -1
                        || options.outHeight == -1) {
                  return null;
                }
                options.inSampleSize = computeSampleSize(
                        options, targetSize, maxPixels);
                options.inJustDecodeBounds = false;

                options.inDither = false;
                options.inPreferredConfig = Bitmap.Config.ARGB_8888;
                bitmap = BitmapFactory.decodeFileDescriptor(fd, null, options);
            } catch (IOException ex) {
                Log.e(TAG, "", ex);
            }
      }

      if (kind == Images.Thumbnails.MICRO_KIND) {
            // now we make it a "square thumbnail" for MICRO_KIND thumbnail
            bitmap = extractThumbnail(bitmap,
                  TARGET_SIZE_MICRO_THUMBNAIL,
                  TARGET_SIZE_MICRO_THUMBNAIL, OPTIONS_RECYCLE_INPUT);
      }
      return bitmap;
    }

    /**
   * Create a video thumbnail for a video. May return null if the video is
   * corrupt or the format is not supported.
   *
   * @param filePath the path of video file
   * @param kind could be MINI_KIND or MICRO_KIND
   */
    public static Bitmap createVideoThumbnail(String filePath, int kind) {
      Bitmap bitmap = null;
      MediaMetadataRetriever retriever = new MediaMetadataRetriever();
      try {
            retriever.setMode(MediaMetadataRetriever.MODE_CAPTURE_FRAME_ONLY);
            retriever.setDataSource(filePath);
            bitmap = retriever.captureFrame();
      } catch (IllegalArgumentException ex) {
            // Assume this is a corrupt video file
      } catch (RuntimeException ex) {
            // Assume this is a corrupt video file.
      } finally {
            try {
                retriever.release();
            } catch (RuntimeException ex) {
                // Ignore failures while cleaning up.
            }
      }
      if (kind == Images.Thumbnails.MICRO_KIND && bitmap != null) {
            bitmap = extractThumbnail(bitmap,
                  TARGET_SIZE_MICRO_THUMBNAIL,
                  TARGET_SIZE_MICRO_THUMBNAIL,
                  OPTIONS_RECYCLE_INPUT);
      }
      return bitmap;
    }

    /**
   * Creates a centered bitmap of the desired size.
   *
   * @param source original bitmap source
   * @param width targeted width
   * @param height targeted height
   */
    public static Bitmap extractThumbnail(
            Bitmap source, int width, int height) {
      return extractThumbnail(source, width, height, OPTIONS_NONE);
    }

    /**
   * Creates a centered bitmap of the desired size.
   *
   * @param source original bitmap source
   * @param width targeted width
   * @param height targeted height
   * @param options options used during thumbnail extraction
   */
    public static Bitmap extractThumbnail(
            Bitmap source, int width, int height, int options) {
      if (source == null) {
            return null;
      }

      float scale;
      if (source.getWidth() < source.getHeight()) {
            scale = width / (float) source.getWidth();
      } else {
            scale = height / (float) source.getHeight();
      }
      Matrix matrix = new Matrix();
      matrix.setScale(scale, scale);
      Bitmap thumbnail = transform(matrix, source, width, height,
                OPTIONS_SCALE_UP | options);
      return thumbnail;
    }

    /*
   * 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 IImage.UNCONSTRAINED,
   * which indicates no care of the corresponding constraint.
   * The functions prefers returning a sample size that
   * generates a smaller bitmap, unless minSideLength = IImage.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.
   */
    private static int computeSampleSize(BitmapFactory.Options options,
            int minSideLength, int maxNumOfPixels) {
      int initialSize = computeInitialSampleSize(options, minSideLength,
                maxNumOfPixels);

      int roundedSize;
      if (initialSize <= 8 ) {
            roundedSize = 1;
            while (roundedSize < initialSize) {
                roundedSize <<= 1;
            }
      } else {
            roundedSize = (initialSize + 7) / 8 * 8;
      }

      return roundedSize;
    }

    private static int computeInitialSampleSize(BitmapFactory.Options options,
            int minSideLength, int maxNumOfPixels) {
      double w = options.outWidth;
      double h = options.outHeight;

      int lowerBound = (maxNumOfPixels == UNCONSTRAINED) ? 1 :
                (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
      int upperBound = (minSideLength == UNCONSTRAINED) ? 128 :
                (int) Math.min(Math.floor(w / minSideLength),
                Math.floor(h / minSideLength));

      if (upperBound < lowerBound) {
            // return the larger one when there is no overlapping zone.
            return lowerBound;
      }

      if ((maxNumOfPixels == UNCONSTRAINED) &&
                (minSideLength == UNCONSTRAINED)) {
            return 1;
      } else if (minSideLength == UNCONSTRAINED) {
            return lowerBound;
      } else {
            return upperBound;
      }
    }

    /**
   * Make a bitmap from a given Uri, minimal side length, and maximum number of pixels.
   * The image data will be read from specified pfd if it's not null, otherwise
   * a new input stream will be created using specified ContentResolver.
   *
   * Clients are allowed to pass their own BitmapFactory.Options used for bitmap decoding. A
   * new BitmapFactory.Options will be created if options is null.
   */
    private static Bitmap makeBitmap(int minSideLength, int maxNumOfPixels,
            Uri uri, ContentResolver cr, ParcelFileDescriptor pfd,
            BitmapFactory.Options options) {
            Bitmap b = null;
      try {
            if (pfd == null) pfd = makeInputStream(uri, cr);
            if (pfd == null) return null;
            if (options == null) options = new BitmapFactory.Options();

            FileDescriptor fd = pfd.getFileDescriptor();
            options.inSampleSize = 1;
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeFileDescriptor(fd, null, options);
            if (options.mCancel || options.outWidth == -1
                  || options.outHeight == -1) {
                return null;
            }
            options.inSampleSize = computeSampleSize(
                  options, minSideLength, maxNumOfPixels);
            options.inJustDecodeBounds = false;

            options.inDither = false;
            options.inPreferredConfig = Bitmap.Config.ARGB_8888;
            b = BitmapFactory.decodeFileDescriptor(fd, null, options);
      } catch (OutOfMemoryError ex) {
            Log.e(TAG, "Got oom exception ", ex);
            return null;
      } finally {
            closeSilently(pfd);
      }
      return b;
    }

    private static void closeSilently(ParcelFileDescriptor c) {
      if (c == null) return;
      try {
          c.close();
      } catch (Throwable t) {
          // do nothing
      }
    }

    private static ParcelFileDescriptor makeInputStream(
            Uri uri, ContentResolver cr) {
      try {
            return cr.openFileDescriptor(uri, "r");
      } catch (IOException ex) {
            return null;
      }
    }

    /**
   * Transform source Bitmap to targeted width and height.
   */
    private static Bitmap transform(Matrix scaler,
            Bitmap source,
            int targetWidth,
            int targetHeight,
            int options) {
      boolean scaleUp = (options & OPTIONS_SCALE_UP) != 0;
      boolean recycle = (options & OPTIONS_RECYCLE_INPUT) != 0;

      int deltaX = source.getWidth() - targetWidth;
      int deltaY = source.getHeight() - targetHeight;
      if (!scaleUp && (deltaX < 0 || deltaY < 0)) {
            /*
            * In this case the bitmap is smaller, at least in one dimension,
            * than the target.Transform it by placing as much of the image
            * as possible into the target and leaving the top/bottom or
            * left/right (or both) black.
            */
            Bitmap b2 = Bitmap.createBitmap(targetWidth, targetHeight,
            Bitmap.Config.ARGB_8888);
            Canvas c = new Canvas(b2);

            int deltaXHalf = Math.max(0, deltaX / 2);
            int deltaYHalf = Math.max(0, deltaY / 2);
            Rect src = new Rect(
            deltaXHalf,
            deltaYHalf,
            deltaXHalf + Math.min(targetWidth, source.getWidth()),
            deltaYHalf + Math.min(targetHeight, source.getHeight()));
            int dstX = (targetWidth- src.width())/ 2;
            int dstY = (targetHeight - src.height()) / 2;
            Rect dst = new Rect(
                  dstX,
                  dstY,
                  targetWidth - dstX,
                  targetHeight - dstY);
            c.drawBitmap(source, src, dst, null);
            if (recycle) {
                source.recycle();
            }
            return b2;
      }
      float bitmapWidthF = source.getWidth();
      float bitmapHeightF = source.getHeight();

      float bitmapAspect = bitmapWidthF / bitmapHeightF;
      float viewAspect   = (float) targetWidth / targetHeight;

      if (bitmapAspect > viewAspect) {
            float scale = targetHeight / bitmapHeightF;
            if (scale < .9F || scale > 1F) {
                scaler.setScale(scale, scale);
            } else {
                scaler = null;
            }
      } else {
            float scale = targetWidth / bitmapWidthF;
            if (scale < .9F || scale > 1F) {
                scaler.setScale(scale, scale);
            } else {
                scaler = null;
            }
      }

      Bitmap b1;
      if (scaler != null) {
            // this is used for minithumb and crop, so we want to filter here.
            b1 = Bitmap.createBitmap(source, 0, 0,
            source.getWidth(), source.getHeight(), scaler, true);
      } else {
            b1 = source;
      }

      if (recycle && b1 != source) {
            source.recycle();
      }

      int dx1 = Math.max(0, b1.getWidth() - targetWidth);
      int dy1 = Math.max(0, b1.getHeight() - targetHeight);

      Bitmap b2 = Bitmap.createBitmap(
                b1,
                dx1 / 2,
                dy1 / 2,
                targetWidth,
                targetHeight);

      if (b2 != b1) {
            if (recycle || b1 != source) {
                b1.recycle();
            }
      }

      return b2;
    }

    /**
   * SizedThumbnailBitmap contains the bitmap, which is downsampled either from
   * the thumbnail in exif or the full image.
   * mThumbnailData, mThumbnailWidth and mThumbnailHeight are set together only if mThumbnail
   * is not null.
   *
   * The width/height of the sized bitmap may be different from mThumbnailWidth/mThumbnailHeight.
   */
    private static class SizedThumbnailBitmap {
      public byte[] mThumbnailData;
      public Bitmap mBitmap;
      public int mThumbnailWidth;
      public int mThumbnailHeight;
    }

    /**
   * Creates a bitmap by either downsampling from the thumbnail in EXIF or the full image.
   * The functions returns a SizedThumbnailBitmap,
   * which contains a downsampled bitmap and the thumbnail data in EXIF if exists.
   */
    private static void createThumbnailFromEXIF(String filePath, int targetSize,
            int maxPixels, SizedThumbnailBitmap sizedThumbBitmap) {
      if (filePath == null) return;

      ExifInterface exif = null;
      byte [] thumbData = null;
      try {
            exif = new ExifInterface(filePath);
            if (exif != null) {
                thumbData = exif.getThumbnail();
            }
      } catch (IOException ex) {
            Log.w(TAG, ex);
      }

      BitmapFactory.Options fullOptions = new BitmapFactory.Options();
      BitmapFactory.Options exifOptions = new BitmapFactory.Options();
      int exifThumbWidth = 0;
      int fullThumbWidth = 0;

      // Compute exifThumbWidth.
      if (thumbData != null) {
            exifOptions.inJustDecodeBounds = true;
            BitmapFactory.decodeByteArray(thumbData, 0, thumbData.length, exifOptions);
            exifOptions.inSampleSize = computeSampleSize(exifOptions, targetSize, maxPixels);
            exifThumbWidth = exifOptions.outWidth / exifOptions.inSampleSize;
      }

      // Compute fullThumbWidth.
      fullOptions.inJustDecodeBounds = true;
      BitmapFactory.decodeFile(filePath, fullOptions);
      fullOptions.inSampleSize = computeSampleSize(fullOptions, targetSize, maxPixels);
      fullThumbWidth = fullOptions.outWidth / fullOptions.inSampleSize;

      // Choose the larger thumbnail as the returning sizedThumbBitmap.
      if (thumbData != null && exifThumbWidth >= fullThumbWidth) {
            int width = exifOptions.outWidth;
            int height = exifOptions.outHeight;
            exifOptions.inJustDecodeBounds = false;
            sizedThumbBitmap.mBitmap = BitmapFactory.decodeByteArray(thumbData, 0,
                  thumbData.length, exifOptions);
            if (sizedThumbBitmap.mBitmap != null) {
                sizedThumbBitmap.mThumbnailData = thumbData;
                sizedThumbBitmap.mThumbnailWidth = width;
                sizedThumbBitmap.mThumbnailHeight = height;
            }
      } else {
            fullOptions.inJustDecodeBounds = false;
            sizedThumbBitmap.mBitmap = BitmapFactory.decodeFile(filePath, fullOptions);
      }
    }
}
页: [1]
查看完整版本: Android缩略图类源代码