Android图片缩放总结及比较
16lz
2022-05-22
在Android中对大图片进行缩放真的很不尽如人意,不知道是不是我的方法不对。下面我列出3种对图片缩放的方法,并给出相应速度。请高人指教。
第一种是BitmapFactory和BitmapFactory.Options。
首先,BitmapFactory.Options有几个Fields很有用:
inJustDecodeBounds:If set to true, the decoder will return null (no bitmap), but the out...
也就是说,当inJustDecodeBounds设成true时,bitmap并不加载到内存,这样效率很高哦。而这时,你可以获得bitmap的高、宽等信息。
outHeight:The resulting height of the bitmap, set independent of the state of inJustDecodeBounds.
outWidth:The resulting width of the bitmap, set independent of the state of inJustDecodeBounds.
看到了吧,上面3个变量是相关联的哦。
inSampleSize : If set to a value > 1, requests the decoder to subsample the original image, returning a smaller image to save memory.
这就是用来做缩放比的。这里有个技巧:
inSampleSize=(outHeight/Height+outWidth/Width)/2
实践证明,这样缩放出来的图片还是很好的。
最后用BitmapFactory.decodeFile(path, options)生成。
由于只是对bitmap加载到内存一次,所以效率比较高。解析速度快。
第二种是使用Bitmap加Matrix来缩放。
ThumbnailUtils源码:
第一种是BitmapFactory和BitmapFactory.Options。
首先,BitmapFactory.Options有几个Fields很有用:
inJustDecodeBounds:If set to true, the decoder will return null (no bitmap), but the out...
也就是说,当inJustDecodeBounds设成true时,bitmap并不加载到内存,这样效率很高哦。而这时,你可以获得bitmap的高、宽等信息。
outHeight:The resulting height of the bitmap, set independent of the state of inJustDecodeBounds.
outWidth:The resulting width of the bitmap, set independent of the state of inJustDecodeBounds.
看到了吧,上面3个变量是相关联的哦。
inSampleSize : If set to a value > 1, requests the decoder to subsample the original image, returning a smaller image to save memory.
这就是用来做缩放比的。这里有个技巧:
inSampleSize=(outHeight/Height+outWidth/Width)/2
实践证明,这样缩放出来的图片还是很好的。
最后用BitmapFactory.decodeFile(path, options)生成。
由于只是对bitmap加载到内存一次,所以效率比较高。解析速度快。
第二种是使用Bitmap加Matrix来缩放。
首先要获得原bitmap,再从原bitmap的基础上生成新图片。这样效率很低。
第三种是用2.2新加的类ThumbnailUtils来做。
让我们新看看这个类,从API中来看,此类就三个静态方法:createVideoThumbnail、extractThumbnail(Bitmap source, int width, int height, int options)、extractThumbnail(Bitmap source, int width, int height)。
我这里使用了第三个方法。再看看它的源码,下面会附上。是上面我们用到的BitmapFactory.Options和Matrix等经过人家一阵加工而成。
效率好像比第二种方法高一点点。
下面是我的例子:
<?xml version="1.0" encoding="utf-8"?>
package com.linc.ResolvePicture;import java.io.File;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import android.app.Activity;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Matrix;import android.graphics.drawable.BitmapDrawable;import android.graphics.drawable.Drawable;import android.media.ThumbnailUtils;import android.os.Bundle;import android.util.Log;import android.widget.ImageView;import android.widget.TextView;public class ResolvePicture extends Activity {private static String tag="ResolvePicture";Drawable bmImg; ImageView imView; ImageView imView2; TextView text; String theTime; long start, stop; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); text=(TextView)findViewById(R.id.text); imView=(ImageView) findViewById(R.id.imageShow); imView2=(ImageView) findViewById(R.id.image2); Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.pic); start=System.currentTimeMillis(); // imView.setImageDrawable(resizeImage(bitmap, 300, 100)); imView2.setImageDrawable(resizeImage2("/sdcard/2.jpeg", 200, 100)); stop=System.currentTimeMillis(); String theTime= String.format("\n1 iterative: (%d msec)", stop - start); start=System.currentTimeMillis(); imView.setImageBitmap(ThumbnailUtils.extractThumbnail(bitmap,200,100));//2.2才加进来的新类,简单易用// imView.setImageDrawable(resizeImage(bitmap, 30, 30)); stop=System.currentTimeMillis(); theTime+= String.format("\n2 iterative: (%d msec)", stop - start); text.setText(theTime); } //使用Bitmap加Matrix来缩放 public static Drawable resizeImage(Bitmap bitmap, int w, int h) { Bitmap BitmapOrg = bitmap; int width = BitmapOrg.getWidth(); int height = BitmapOrg.getHeight(); int newWidth = w; int newHeight = h; float scaleWidth = ((float) newWidth) / width; float scaleHeight = ((float) newHeight) / height; Matrix matrix = new Matrix(); matrix.postScale(scaleWidth, scaleHeight); // if you want to rotate the Bitmap // matrix.postRotate(45); Bitmap resizedBitmap = Bitmap.createBitmap(BitmapOrg, 0, 0, width, height, matrix, true); return new BitmapDrawable(resizedBitmap); } //使用BitmapFactory.Options的inSampleSize参数来缩放 public static Drawable resizeImage2(String path,int width,int height) {BitmapFactory.Options options = new BitmapFactory.Options();options.inJustDecodeBounds = true;//不加载bitmap到内存中BitmapFactory.decodeFile(path,options); int outWidth = options.outWidth;int outHeight = options.outHeight;options.inDither = false;options.inPreferredConfig = Bitmap.Config.ARGB_8888;options.inSampleSize = 1;if (outWidth != 0 && outHeight != 0 && width != 0 && height != 0) {int sampleSize=(outWidth/width+outHeight/height)/2;Log.d(tag, "sampleSize = " + sampleSize);options.inSampleSize = sampleSize;}options.inJustDecodeBounds = false;return new BitmapDrawable(BitmapFactory.decodeFile(path, options));} //图片保存 private void saveThePicture(Bitmap bitmap) { File file=new File("/sdcard/2.jpeg"); try { FileOutputStream fos=new FileOutputStream(file); if(bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos)) { fos.flush(); fos.close(); } } catch(FileNotFoundException e1) { e1.printStackTrace(); } catch(IOException e2) { e2.printStackTrace(); } }}
ThumbnailUtils源码:
/* * 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); } }}
更多相关文章
- android浮动搜索框
- 【Android】android开发---实现屏幕旋转的两种方法
- 搭建 Android(安卓)X86 系统,替代AVD模拟器,解决运行效率慢的问题(
- Android中当item数量超过一定大小RecyclerView高度固定
- Android(安卓)菜单(OptionMenu)大全 建立你自己的菜单
- [转]]Android 应用签名提权方法
- 最受欢迎的文章汇总
- 浅谈Java中Collections.sort对List排序的两种方法
- Python list sort方法的具体使用