安卓开发图片压缩一直是一个头痛的问题,一不小心就会
oom
。 我对一个github
上的库就行了简单的改写,把代码记录下来,自己也梳理了下图片压缩的过程。
尺寸压缩
尺寸压缩也就是按比例压缩尺寸,当我们需要显示图片时,控件加载的是
bitmap
,而bitmap
的大小主要和尺寸和图片格式有关,这时候我们不需要进行质量压缩
- 计算采样比(图片压缩比例)
private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { // 获得原始宽高 final int height = options.outHeight; final int width = options.outWidth; //默认为1(不压缩) int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { //压缩到宽高都小于我们要求的大小 while ((height / inSampleSize) >= reqHeight || (width / inSampleSize) >= reqWidth) { //android内部只会取2的倍数,也就是一半一半的压缩 inSampleSize *= 2; } } return inSampleSize;}复制代码
- 压缩尺寸
static Bitmap decodeSampledBitmapFromFile(File imageFile, int reqWidth, int reqHeight) throws IOException { BitmapFactory.Options options = new BitmapFactory.Options(); //设置inJustDecodeBounds=true,时BitmapFactory加载图片不返回bitmap,只返回信息,减少内存开销 options.inJustDecodeBounds = true; //相当于加载空图片获取尺寸信息 BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options); //用我们上面的方法计算压缩比例 options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); //设置inJustDecodeBounds=false,返回bitmap options.inJustDecodeBounds = false; Bitmap scaledBitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options); //把图片旋转成正确的方向 ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath()); int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0); Matrix matrix = new Matrix(); if (orientation == 6) { matrix.postRotate(90); } else if (orientation == 3) { matrix.postRotate(180); } else if (orientation == 8) { matrix.postRotate(270); } //复制一份旋转后的bitmap后返回 scaledBitmap = Bitmap.createBitmap(scaledBitmap, 0, 0, scaledBitmap.getWidth(), scaledBitmap.getHeight(), matrix, true); return scaledBitmap;}复制代码
质量压缩
质量压缩代码比较简单但要注意非常耗时
private static ByteArrayOutputStream compressBitmapSize(Bitmap bitmap, Bitmap.CompressFormat compressFormat, int defaultQuality, long maxSize) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); int quality = defaultQuality; bitmap.compress(compressFormat, quality, baos); //当大于我们指定的大小时,我就继续压缩 while (baos.toByteArray().length / 1024 > maxSize) { if (quality <= 10) { //压缩比例要大于0 break; } else { baos.reset(); quality -= 10; //quality 表示压缩多少 100 表示不压缩 bitmap.compress(compressFormat, quality, baos); } } return baos; }复制代码
完整的压缩工具类
public class ImageUtil { //compress main way static File compressImage(File imageFile, long size, int reqWith, int reqHeight, Bitmap.CompressFormat compressFormat, int quantity, String destinationPath) throws IOException { FileOutputStream fileOutputStream = null; File file = new File(destinationPath).getParentFile(); if (!file.exists()) { file.mkdirs(); } try { fileOutputStream = new FileOutputStream(destinationPath); //先压缩尺寸,防止内存溢出 Bitmap bitmap = decodeSampledBitmapFromFile(imageFile, reqWith, reqHeight); //ByteArrayOutputStream 不需要关闭 //压缩尺寸 ByteArrayOutputStream baos = compressBitmapSize(bitmap, compressFormat, quantity, size); fileOutputStream.write(baos.toByteArray()); } finally { if (fileOutputStream != null) { fileOutputStream.flush(); fileOutputStream.close(); } } return new File(destinationPath); } //按照比例压缩图片 static Bitmap decodeSampledBitmapFromFile(File imageFile, int reqWidth, int reqHeight) throws IOException { BitmapFactory.Options options = new BitmapFactory.Options(); //just need size options.inJustDecodeBounds = true; BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options); //calculate inSampleSize options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); //real decode bitmap options.inJustDecodeBounds = false; Bitmap scaledBitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options); //rotation of the image ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath()); int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0); Matrix matrix = new Matrix(); if (orientation == 6) { matrix.postRotate(90); } else if (orientation == 3) { matrix.postRotate(180); } else if (orientation == 8) { matrix.postRotate(270); } scaledBitmap = Bitmap.createBitmap(scaledBitmap, 0, 0, scaledBitmap.getWidth(), scaledBitmap.getHeight(), matrix, true); return scaledBitmap; } //获取采样(压缩比) private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { // Raw height and width of image final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { while ((height / inSampleSize) >= reqHeight || (width / inSampleSize) >= reqWidth) { inSampleSize *= 2; } } return inSampleSize; } //压缩尺寸 private static ByteArrayOutputStream compressBitmapSize(Bitmap bitmap, Bitmap.CompressFormat compressFormat, int defaultQuality, long maxSize) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); int quality = defaultQuality; bitmap.compress(compressFormat, quality, baos); while (baos.toByteArray().length / 1024 > maxSize) { if (quality <= 10) { // quality must >0 break; } else { baos.reset(); quality -= 10; bitmap.compress(compressFormat, quality, baos); } } return baos; }}复制代码
封装成一个工厂模式工具类
public class Compressor { private long maxSize = 1024; //1024kb private int maxWidth = 800; private int maxHeight = 800; private Bitmap.CompressFormat compressFormat = Bitmap.CompressFormat.JPEG; private int quality = 80; private String destinationDirectoryPath; public Compressor(Context context) { destinationDirectoryPath = context.getCacheDir().getPath() + File.separator + "images"; } public Compressor setMaxSize(long size) { this.maxSize = size; return this; } public Compressor setMaxWidth(int maxWidth) { this.maxWidth = maxWidth; return this; } public Compressor setMaxHeight(int maxHeight) { this.maxHeight = maxHeight; return this; } public Compressor setCompressFormat(Bitmap.CompressFormat compressFormat) { this.compressFormat = compressFormat; return this; } public Compressor setQuality(int quality) { this.quality = quality; return this; } public Compressor setDestinationDirectoryPath(String destinationDirectoryPath) { this.destinationDirectoryPath = destinationDirectoryPath; return this; } //压缩到文件 public File compressToFile(File imageFile, String compressedFileName) throws IOException { return ImageUtil.compressImage(imageFile, maxSize, maxWidth, maxHeight, compressFormat, quality, destinationDirectoryPath + File.separator + compressedFileName); } //只压缩尺寸 public Bitmap compressToBitmap(File imageFile) throws IOException { return ImageUtil.decodeSampledBitmapFromFile(imageFile, maxWidth, maxHeight); }}复制代码
使用
推荐大家使用WEBP格式的图片,内存更小(png无损格式很大)而且不会损失透明度(jpg没有透明)
- 简单用法
File file = new Compressor(CompressorActivity.this) .compressToFile(getExternalCacheDir().getAbsoluteFile(), UUID.randomUUID().toString() + ".jpg");复制代码
- 指定压缩的参数
File file = new Compressor(CompressorActivity.this) .setMaxWidth(1080) .setMaxHeight(1080) .setQuality(75) .setMaxSize(100) .setCompressFormat(Bitmap.CompressFormat.WEBP) .setDestinationDirectoryPath(getExternalCacheDir().getAbsolutePath()) .compressToFile(new File(getPath(CompressorActivity.this, uri)), UUID.randomUUID().toString() + ".webp");复制代码