package net.dev123.yibo.common; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import net.dev123.commons.http.HttpRequestHelper; import net.dev123.commons.util.FileUtil; import net.dev123.commons.util.StringUtil; import net.dev123.exception.LibException; import android.content.ContentResolver; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.PorterDuff.Mode; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.RectF; import android.net.Uri; import android.os.ParcelFileDescriptor; import android.util.Log; public class ImageUtil { private ImageUtil() { } public static final String TAG = ImageUtil.class.getSimpleName(); public static final long MAX_BITMAP_SIZE = 1024 * 1024 * 4; //允许的图片最大值; public static final long SAFE_DISTANCE = 1024 * 100; //至少要保留多大预留空间; public static final long SCALE_BOUND_SIZE = 1024; //图片宽高最小值大于这个时,选择缩小图片而不是画质. public static final int ROTATE_MAX_SIZE = 512; //旋转图片时,图片缩放的最大尺寸 public static final int UNCONSTRAINED = -1; /* * 进行图片缩放 */ public static Bitmap scaleBitmapTo(Bitmap bitmap, int targetSize) { if (bitmap == null) { return null; } int srcWidth = bitmap.getWidth(); int srcHeight = bitmap.getHeight(); int dstWidth = targetSize; int dstHeight = targetSize; if (srcWidth > srcHeight) { dstHeight = ((targetSize * srcHeight) / srcWidth); } else { dstWidth = ((targetSize * srcWidth) / srcHeight); } Bitmap dstBitmap = Bitmap.createScaledBitmap(bitmap, dstWidth, dstHeight, true); return dstBitmap; } /* * 进行图片缩放 */ public static Bitmap scaleImageFile(File imageFile, int size) { if (imageFile == null || !imageFile.isFile() || !imageFile.exists() || size <= 0) { return null; } BitmapFactory.Options options = new BitmapFactory.Options(); //先指定原始大小 options.inSampleSize = 1; //只进行大小判断 options.inJustDecodeBounds = true; //调用此方法得到options得到图片的大小 BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options); //获得缩放比例 options.inSampleSize = getScaleSampleSize(options, size); //OK,我们得到了缩放的比例,现在开始正式读入BitMap数据 options.inJustDecodeBounds = false; options.inDither = false; options.inPreferredConfig = Bitmap.Config.ARGB_8888; //根据options参数,减少所需要的内存 Bitmap sourceBitmap = null; sourceBitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options); return sourceBitmap; } /* * 进行图片缩放 */ public static boolean scaleImageFile(File source, File dest, int size) { boolean isSuccess = false; if (source == null || !source.isFile() || !source.exists() || dest == null || size <= 0) { return isSuccess; } Bitmap bitmap = scaleImageFile(source, size); if (bitmap == null) { return isSuccess; } FileOutputStream fos = null; try { if (!dest.exists()) { dest.createNewFile(); } fos = new FileOutputStream(dest); bitmap.compress(getCompressFormat(dest), 100, fos); isSuccess = true; } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (fos != null) { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } } bitmap.recycle(); return isSuccess; } /* * 进行图片缩放 * @param: resolver * @param: url * @param: size 目标缩放大小 */ public static Bitmap scaleImageUriTo(ContentResolver resolver, Uri uri, int size) { if (resolver == null || uri == null || size <= 0) { return null; } ParcelFileDescriptor pfd; try { pfd = resolver.openFileDescriptor(uri, "r"); } catch (IOException ex) { if (Constants.DEBUG) Log.e(TAG, "", ex); return null; } java.io.FileDescriptor fd = pfd.getFileDescriptor(); BitmapFactory.Options options = new BitmapFactory.Options(); //先指定原始大小 options.inSampleSize = 1; //只进行大小判断 options.inJustDecodeBounds = true; //调用此方法得到options得到图片的大小 BitmapFactory.decodeFileDescriptor(fd, null, options); //获得缩放比例 options.inSampleSize = getScaleSampleSize(options, size); //OK,我们得到了缩放的比例,现在开始正式读入BitMap数据 options.inJustDecodeBounds = false; options.inDither = false; options.inPreferredConfig = Bitmap.Config.ARGB_8888; //根据options参数,减少所需要的内存 Bitmap sourceBitmap = null; sourceBitmap = BitmapFactory.decodeFileDescriptor(fd, null, options); return sourceBitmap; } /* * 这个函数会对图片的大小进行判断,并得到合适的缩放比例,比如2即1/2,3即1/3 * @target: 要缩放成的宽或高 * * @return: 缩放比例 */ public static int getScaleSampleSize(BitmapFactory.Options options, int target) { if (options == null || target <= 0) { return 1; } int w = options.outWidth; int h = options.outHeight; int candidateW = w / target; int candidateH = h / target; int candidate = Math.max(candidateW, candidateH); if (candidate == 0) return 1; if (candidate > 1) { if ((w > target) && (w / candidate) < target) { candidate -= 1; } } if (candidate > 1) { if ((h > target) && (h / candidate) < target) { candidate -= 1; } } return candidate; } /* * 图片处理成圆角 */ public static Bitmap getRoundedCornerBitmap(Bitmap bitmap) { if (bitmap == null) { return null; } Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Config.ARGB_8888); Canvas canvas = new Canvas(output); final int color = 0xff424242; final Paint paint = new Paint(); final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); final RectF rectF = new RectF(rect); final int minBound = Math.min(bitmap.getWidth(), bitmap.getHeight()); final float roundPx = minBound * 4/50; paint.setAntiAlias(true); canvas.drawARGB(0, 0, 0, 0); paint.setColor(color); canvas.drawRoundRect(rectF, roundPx, roundPx, paint); paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN)); canvas.drawBitmap(bitmap, rect, rect, paint); return output; } /* * 获取网络上的图片,用于取代BitmapFactory.decodeStream(is); * 防止由于设备或网络不好,无法取到图片; */ public static Bitmap getBitmapByUrl(String imgUrl) throws LibException { Bitmap bitmap = null; byte[] imgBytes = getByteArrayByUrl(imgUrl); if (imgBytes == null) { return bitmap; } bitmap = decodeByteArray(imgBytes); return bitmap; } public static byte[] getByteArrayByUrl(String imgUrl) throws LibException { if (StringUtil.isEmpty(imgUrl)) { return null; } byte[] imgBytes = HttpRequestHelper.getContentBytes(imgUrl); return imgBytes; } public static File getFileByUrl(String imgUrl, File destFile) throws LibException { if (StringUtil.isEmpty(imgUrl) || destFile == null) { return null; } File saveFile = HttpRequestHelper.getBitmapFile(imgUrl, destFile); return saveFile; } public static Bitmap decodeByteArray(byte[] imgBytes) { Bitmap bitmap = null; if (imgBytes == null || imgBytes.length == 0) { return bitmap; } BitmapFactory.Options options = new BitmapFactory.Options(); //先指定原始大小 options.inSampleSize = 1; //只进行大小判断 options.inJustDecodeBounds = true; options.inPreferredConfig = Bitmap.Config.ARGB_8888; //Bitmap.Config.ARGB_8888 像素深度为4,导致占用内存加倍 BitmapFactory.decodeByteArray(imgBytes, 0, imgBytes.length, options); long bitmapSize = ImageUtil.calculateBitmapSize(options); if (bitmapSize - ImageUtil.SAFE_DISTANCE >= MemoryManager.getAvailableNativeMemorySize() || bitmapSize - ImageUtil.SAFE_DISTANCE >= ImageUtil.MAX_BITMAP_SIZE) { Log.d(TAG, "Image size(" + bitmapSize + ") is beyond allowed Size, we will first reclaim memory!"); Runtime.getRuntime().gc(); if (Math.min(options.outWidth, options.outHeight) > ImageUtil.SCALE_BOUND_SIZE) { options.inSampleSize = 2; } else { options.inPreferredConfig = Bitmap.Config.ARGB_4444; //降低清晰度; } BitmapFactory.decodeByteArray(imgBytes, 0, imgBytes.length, options); bitmapSize = ImageUtil.calculateBitmapSize(options); } if (bitmapSize >= MemoryManager.getAvailableNativeMemorySize() || bitmapSize >= ImageUtil.MAX_BITMAP_SIZE) { Log.d(TAG, "Image size(" + bitmapSize + ") is beyond allowed Size, but will scale it!"); long targetSize = Math.min(MemoryManager.getAvailableNativeMemorySize(), ImageUtil.MAX_BITMAP_SIZE); int size = (options.inSampleSize > 0 ? options.inSampleSize : 1); options.inJustDecodeBounds = true; while (bitmapSize > targetSize) { size++; options.inSampleSize = size; BitmapFactory.decodeByteArray(imgBytes, 0, imgBytes.length, options); bitmapSize = ImageUtil.calculateBitmapSize(options); } Log.d(TAG, "after scaled, Image size is (" + bitmapSize + ")"); } options.inJustDecodeBounds = false; options.inDither = false; if (Constants.DEBUG) MemoryManager.trace(); bitmap = BitmapFactory.decodeByteArray(imgBytes, 0, imgBytes.length, options); return bitmap; } /* * 计算图片在内存中,占的大小 * 2 是像素的深度,一般占用两个字节 */ public static long calculateBitmapSize(BitmapFactory.Options options) { int depth = 2; int sampleSize = 1; if (options.inPreferredConfig == null) { depth = 2; } else { switch (options.inPreferredConfig) { case ARGB_4444: depth = 2; break; case ARGB_8888: depth = 4; break; default: depth = 2; break; } } if (options.inSampleSize > 1) { sampleSize = options.inSampleSize; } if (Constants.DEBUG) { Log.d(TAG, "image widht:" + options.outWidth + ", hight:" + options.outHeight + ", inSampleSize:" + sampleSize); } return options.outWidth * options.outHeight * depth; //options.outWidth * options.outHeight * depth / sampleSize } /** * 旋转图片 * * @param bitmap 图片 * @param degrees 角度 * @return 旋转后的图片Bitmap对象 */ public static Bitmap rotate(Bitmap bitmap, int degrees) { if (degrees != 0 && bitmap != null) { Matrix matrix = new Matrix(); matrix.setRotate(degrees, (float) bitmap.getWidth() / 2, (float) bitmap.getHeight() / 2); try { Bitmap rotated = Bitmap.createBitmap( bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); if (bitmap != rotated) { bitmap.recycle(); bitmap = rotated; } } catch (OutOfMemoryError ex) { // We have no memory to rotate. Return the original bitmap. } } return bitmap; } /** * 旋转图片文件 * * @param imageFile 图片文件 * @param degrees 角度 * @return 缩放并旋转后的图片文件 * @throws IOException */ public static boolean rotateImageFile(File imageFile, File dest, int degrees) { boolean isSuccess = false; if (imageFile == null || !imageFile.isFile() || !imageFile.exists() || dest == null) { return isSuccess; } Bitmap bitmap = scaleImageFile(imageFile, ROTATE_MAX_SIZE); Bitmap rotated = rotate(bitmap, degrees); //翻转图片 FileOutputStream fileOutputStream = null; try { if (!dest.exists()) { dest.createNewFile(); } fileOutputStream = new FileOutputStream(dest); rotated.compress(getCompressFormat(dest), 100, fileOutputStream); isSuccess = true; } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (fileOutputStream != null) { try { fileOutputStream.flush(); fileOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } bitmap.recycle(); rotated.recycle(); return isSuccess; } private static Bitmap.CompressFormat getCompressFormat(File file) { Bitmap.CompressFormat format = null; try { String fileExtension = FileUtil.getFileExtensionFromName(file.getName()); format = Bitmap.CompressFormat.valueOf(fileExtension); } catch (Exception e) { format = Bitmap.CompressFormat.JPEG; } return format; } public static Uri getImageUri(String path) { return Uri.fromFile(new File(path)); } public static Bitmap getBitmap(String path) { try { InputStream in = new FileInputStream(path); return BitmapFactory.decodeStream(in); } catch (FileNotFoundException e) { return null; } } public static BitmapFactory.Options getBitmapOptions(String path) { BitmapFactory.Options options = new BitmapFactory.Options(); if (StringUtil.isEmpty(path)) { return options; } File imageFile = new File(path); if (imageFile == null || !imageFile.isFile() || !imageFile.exists()) { return options; } options = new BitmapFactory.Options(); //先指定原始大小 options.inSampleSize = 1; //只进行大小判断 options.inJustDecodeBounds = true; //调用此方法得到options得到图片的大小 BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options); return options; } public static final int OPTIONS_NONE = 0x0; public 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; /** * 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; } /** * Transform source Bitmap to targeted width and height. */ public 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; } /* * 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(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; } public 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; } } private static int computeSampleSize(InputStream stream, int maxResolutionX, int maxResolutionY) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeStream(stream, null, options); int maxNumOfPixels = maxResolutionX * maxResolutionY; int minSideLength = Math.min(maxResolutionX, maxResolutionY) / 2; return computeSampleSize(options, minSideLength, maxNumOfPixels); } public static final Bitmap createBitmapFromUri(Context context, String uri, int maxResolutionX, int maxResolutionY) throws FileNotFoundException { final BitmapFactory.Options options = new BitmapFactory.Options(); options.inScaled = false; options.inPreferredConfig = Bitmap.Config.ARGB_8888; options.inDither = true; Bitmap bitmap = null; // Get the input stream for computing the sample size. BufferedInputStream bufferedInput = null; if (uri.startsWith(ContentResolver.SCHEME_CONTENT) || uri.startsWith(ContentResolver.SCHEME_FILE)) { // Get the stream from a local file. bufferedInput = new BufferedInputStream(context.getContentResolver() .openInputStream(Uri.parse(uri)), 16348); // 16 * 1024 } // Compute the sample size, i.e., not decoding real pixels. if (bufferedInput != null) { options.inSampleSize = computeSampleSize(bufferedInput, maxResolutionX, maxResolutionY); } else { return null; } // Get the input stream again for decoding it to a bitmap. bufferedInput = null; if (uri.startsWith(ContentResolver.SCHEME_CONTENT) || uri.startsWith(ContentResolver.SCHEME_FILE)) { // Get the stream from a local file. bufferedInput = new BufferedInputStream(context.getContentResolver() .openInputStream(Uri.parse(uri)), 16384); } // Decode bufferedInput to a bitmap. if (bufferedInput != null) { options.inDither = false; options.inJustDecodeBounds = false; Thread timeoutThread = new Thread("BitmapTimeoutThread") { public void run() { try { Thread.sleep(6000); options.requestCancelDecode(); } catch (InterruptedException e) { } } }; timeoutThread.start(); bitmap = BitmapFactory.decodeStream(new FlushedInputStream(bufferedInput), null, options); } return bitmap; } public static final Bitmap resizeBitmap(Bitmap bitmap, int maxSize) { int srcWidth = bitmap.getWidth(); int srcHeight = bitmap.getHeight(); int width = maxSize; int height = maxSize; boolean needsResize = false; if (srcWidth > srcHeight) { if (srcWidth > maxSize) { needsResize = true; height = ((maxSize * srcHeight) / srcWidth); } } else { if (srcHeight > maxSize) { needsResize = true; width = ((maxSize * srcWidth) / srcHeight); } } if (needsResize) { Bitmap retVal = Bitmap.createScaledBitmap(bitmap, width, height, true); return retVal; } else { return bitmap; } } /** * @see <a href='http://code.google.com/p/android/issues/detail?id=6066'> * <code>BitmapFactory.decodeStream()</code> fails * if <code>InputStream.skip()</code> does not skip fully * </a> */ static class FlushedInputStream extends FilterInputStream { public FlushedInputStream(InputStream inputStream) { super(inputStream); } @Override public long skip(long n) throws IOException { long totalBytesSkipped = 0L; while (totalBytesSkipped < n) { long bytesSkipped = in.skip(n - totalBytesSkipped); if (bytesSkipped == 0L) { if (read() < 0) { break; // we reached EOF } else { bytesSkipped = 1; // we read one byte } } totalBytesSkipped += bytesSkipped; } return totalBytesSkipped; } } }