1. 程式人生 > >Android的圖片壓縮類ThumbnailUtils,供原始碼研究

Android的圖片壓縮類ThumbnailUtils,供原始碼研究

考慮到有的哥們沒有下載相關原始碼 下面則是:[java]  
 /*
 * Copyright (C) 2009 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 android.media; 
 
import android.content.ContentResolver; 
import android.content.ContentUris; 
import android.content.ContentValues; 
import android.database.Cursor; 
import android.graphics.Bitmap; 
import android.graphics.BitmapFactory; 
import android.graphics.Canvas; 
import android.graphics.Matrix; 
import android.graphics.Rect; 
import android.media.MediaMetadataRetriever; 
import android.media.MediaFile.MediaFileType; 
import android.net.Uri; 
import android.os.ParcelFileDescriptor; 
import android.provider.BaseColumns; 
import android.provider.MediaStore.Images; 
import android.provider.MediaStore.Images.Thumbnails; 
import android.util.Log; 
 
import java.io.FileInputStream; 
import java.io.FileDescriptor; 
import java.io.IOException; 
import java.io.OutputStream; 
 
/**
 * Thumbnail generation routines for media provider.
 */ 
 
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); 
        } 
    } 
}