android加载大图的几种压缩方法

原文链接

自定义压缩工具类

/**
 * PictureCompressUtil
 * 图像压缩工厂类
 *
 * compressAndGenImage()  将图片按质量压缩成指定大小,并将图像生成指定的路径
 * compressBySampleSize() 将图片按采样率进行压缩,并将图像生成指定的路径
 *
 * @author hzy
 * 
 */
public class PictureCompressUtil {


    /**
     * 按质量压缩,并将图像生成指定的路径
     *
     * @param imgPath
     * @param outPath
     * @param maxSize      目标将被压缩到小于这个大小(KB)。
     * @param needsDelete  是否压缩后删除原始文件
     * @throws IOException
     */
    public void compressByQuality(String imgPath, String outPath, int maxSize, boolean needsDelete)
            throws IOException {
        compressAndGenImage(getBitmap(imgPath), outPath, maxSize);

        // Delete original file
        if (needsDelete) {
            File file = new File(imgPath);
            if (file.exists()) {
                file.delete();
            }
        }
    }


    /**
     * 压缩图片(质量压缩)
     *
     * @param bm       图片格式 jpeg,png,webp
     * @param quality  图片的质量,0-100,数值越小质量越差
     * @param maxSize  压缩后图片的最大kb值
     */
    public static File compressByQuality(Bitmap bm,int quality,int maxSize) {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        bm.compress(Bitmap.CompressFormat.JPEG, quality, bos);// 质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
        long length=bos.toByteArray().length;
        while (length/ 1024 > maxSize) { // 循环判断如果压缩后图片是否大于minSize,大于继续压缩
            bos.reset();// 重置bos即清空bos
            quality -= 5;// 每次都减少5
            bm.compress(Bitmap.CompressFormat.JPEG, quality, bos);// 这里压缩options%,把压缩后的数据存放到baos中
            length = bos.toByteArray().length;
        }
        SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss");
        Date date = new Date(System.currentTimeMillis());
        String filename = format.format(date);
        File file = new File(Environment.getExternalStorageDirectory(), filename + ".png");
        try {
            FileOutputStream fos = new FileOutputStream(file);
            try {
                fos.write(bos.toByteArray());
                fos.flush();
                fos.close();
            } catch (IOException e) {
 
                e.printStackTrace();
            }
        } catch (FileNotFoundException e) {
 
            e.printStackTrace();
        }
        recycleBitmap(bm);
        return file;
    }
 
    public static void recycleBitmap(Bitmap... bitmaps) {
        if (bitmaps == null) {
            return;
        }
        for (Bitmap bm : bitmaps) {
            if (null != bm && !bm.isRecycled()) {
                bm.recycle();
            }
        }
    }
 

 
    /**
     * 通过像素压缩图像,这将改变图像的宽度/高度。用于获取缩略图
     *
     * @param imgPath   image path
     * @param pixelW    目标宽度像素
     * @param pixelH    高度目标像素
     * @return
     */
    public Bitmap ratio(String imgPath, float pixelW, float pixelH) {
        BitmapFactory.Options newOpts = new BitmapFactory.Options();
        // 开始读入图片,此时把options.inJustDecodeBounds 设回true,即只读边不读内容
        newOpts.inJustDecodeBounds = true;
        newOpts.inPreferredConfig = Config.RGB_565;
        // 获取位图信息,但请注意位图现在为空
        Bitmap bitmap = BitmapFactory.decodeFile(imgPath, newOpts);
 
        newOpts.inJustDecodeBounds = false;
        int w = newOpts.outWidth;
        int h = newOpts.outHeight;
        // 想要缩放的目标尺寸,现在大部分手机都是1080*1920,参考值可以让宽高都缩小一倍
        float hh = pixelH;// 设置高度为960f时,可以明显看到图片缩小了
        float ww = pixelW;// 设置宽度为540f,可以明显看到图片缩小了
        // 缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
        int be = 1;// be=1表示不缩放
        if (w > h && w > ww) {// 如果宽度大的话根据宽度固定大小缩放
            be = (int) (newOpts.outWidth / ww);
        } else if (w < h && h > hh) {// 如果高度高的话根据宽度固定大小缩放
            be = (int) (newOpts.outHeight / hh);
        }
        if (be <= 0)
            be = 1;
        newOpts.inSampleSize = be;// 设置缩放比例
        // 开始压缩图片,注意此时已经把options.inJustDecodeBounds 设回false了
        bitmap = BitmapFactory.decodeFile(imgPath, newOpts);
        // 压缩好比例大小后再进行质量压缩
//      return compressByQuality(bitmap, 100,1000); // 这里再进行质量压缩的意义不大,反而耗资源,删除
        return bitmap;
    }
 
    /**
     * 压缩图像的大小,这将修改图像宽度/高度。用于获取缩略图
     *
     * @param bm
     * @param pixelW  target pixel of width
     * @param pixelH  target pixel of height
     * @return
     */
    public Bitmap ratio(Bitmap bm, float pixelW, float pixelH) {
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        bm.compress(Bitmap.CompressFormat.JPEG, 100, os);
        if (os.toByteArray().length / 1024 > 1024) {// 判断如果图片大于1M,进行压缩避免在生成图片(BitmapFactory.decodeStream)时溢出
            os.reset();// 重置baos即清空baos
            bm.compress(Bitmap.CompressFormat.JPEG, 50, os);// 这里压缩50%,把压缩后的数据存放到baos中
        }
        ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
        BitmapFactory.Options newOpts = new BitmapFactory.Options();
        // 开始读入图片,此时把options.inJustDecodeBounds 设回true了
        newOpts.inJustDecodeBounds = true;
        newOpts.inPreferredConfig = Config.RGB_565;
        Bitmap bitmap = BitmapFactory.decodeStream(is, null, newOpts);
        newOpts.inJustDecodeBounds = false;
        int w = newOpts.outWidth;
        int h = newOpts.outHeight;
        // 想要缩放的目标尺寸,现在大部分手机都是1080*1920,参考值可以让宽高都缩小一倍
        float hh = pixelH;// 设置高度为960f时,可以明显看到图片缩小了
        float ww = pixelW;// 设置宽度为540f,可以明显看到图片缩小了
        // 缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
        int be = 1;// be=1表示不缩放
        if (w > h && w > ww) {// 如果宽度大的话根据宽度固定大小缩放
            be = (int) (newOpts.outWidth / ww);
        } else if (w < h && h > hh) {// 如果高度高的话根据宽度固定大小缩放
            be = (int) (newOpts.outHeight / hh);
        }
        if (be <= 0)
            be = 1;
        newOpts.inSampleSize = be;// 设置缩放比例
        // 重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了
        is = new ByteArrayInputStream(os.toByteArray());
        bitmap = BitmapFactory.decodeStream(is, null, newOpts);
        // 压缩好比例大小后再进行质量压缩
        // return compress(bitmap, maxSize); // 这里再进行质量压缩的意义不大,反而耗资源,删除
        return bitmap;
    }
 

 


    /**
     * 按质量压缩,并将图像生成指定的路径
     *
     * @param bm
     * @param outPath
     * @param maxSize 目标将被压缩到小于这个大小(KB)。
     * @throws IOException
     */
    public void compressAndGenImage(Bitmap bm, String outPath, int maxSize) throws IOException {
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        // scale
        int options = 100;
        // Store the bitmap into output stream(no compress)
        bm.compress(Bitmap.CompressFormat.JPEG, options, os);
        // Compress by loop
        while (os.toByteArray().length / 1024 > maxSize) {
            // Clean up os
            os.reset();
            // interval 10
            options -= 10;
            bm.compress(Bitmap.CompressFormat.JPEG, options, os);
        }

        // Generate compressed image file
        FileOutputStream fos = new FileOutputStream(outPath);
        fos.write(os.toByteArray());
        fos.flush();
        fos.close();
    }
 
    /**
     * 比例和生成图片的路径指定
     * 
     * @param bm
     * @param outPath
     * @param pixelW 目标宽度像素
     * @param pixelH 高度目标像素
     * @throws FileNotFoundException
     */
    public void compressBySampleSize(Bitmap bm, String outPath, float pixelW, float pixelH)
            throws FileNotFoundException {
        Bitmap bitmap = ratio(bm, pixelW, pixelH);
        storeImage(bitmap, outPath);
    }
 
    /**
     * 比例生成图片的路径指定
     * 
     * @param imgPath
     * @param outPath
     * @param pixelW 目标宽度像素
     * @param pixelH 高度目标像素
     * @param needsDelete 是否压缩后删除原始文件
     * @throws FileNotFoundException
     */
    public void compressBySampleSize(String imgPath, String outPath, float pixelW, float pixelH, boolean needsDelete)
            throws FileNotFoundException {
        Bitmap bitmap = ratio(imgPath, pixelW, pixelH);
        storeImage(bitmap, outPath);
        if (needsDelete) {
            File file = new File(imgPath);
            if (file.exists()) {
                file.delete();
            }
        }
    }

    /**
     * 将位图存储到指定的图像路径中
     *
     * @param bm
     * @param outPath
     * @throws FileNotFoundException
     */
    public void storeImage(Bitmap bm, String outPath) throws FileNotFoundException {
        FileOutputStream os = new FileOutputStream(outPath);
        bm.compress(Bitmap.CompressFormat.JPEG, 100, os);
    }

    /**
     * 从指定的图像路径获取位图
     * @param imgPath
     * @return
     */
    public Bitmap getBitmap(String imgPath) {
        BitmapFactory.Options newOpts = new BitmapFactory.Options();
        newOpts.inJustDecodeBounds = false;
        newOpts.inPurgeable = true;
        newOpts.inInputShareable = true;
        newOpts.inSampleSize = 1;
        newOpts.inPreferredConfig = Config.RGB_565;//设置RGB
        return BitmapFactory.decodeFile(imgPath, newOpts);
    }
 
}

常用的压缩方法详解

压缩方法分为:
1、质量压缩

ByteArrayOutputStream baos = new ByteArrayOutputStream();
            bit.compress(CompressFormat.JPEG, quality, baos);
            byte[] bytes = baos.toByteArray();
            bm = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);

            Log.i("pic", "压缩后图片的大小" + (bm.getByteCount() / 1024 / 1024)
                    + "M宽度为" + bm.getWidth() + "高度为" + bm.getHeight()
                    + "bytes.length=  " + (bytes.length / 1024) + "KB"
                    + "quality=" + quality);

质量压缩不会减少图片的像素,它是在保持像素的前提下改变图片的位深及透明度,来达到压缩图片的目的,图片的长,宽,像素都不会改变,那么bitmap所占内存大小是不会变的。
  我们可以看到有个参数:quality,可以调节你压缩的比例,但是还要注意一点就是,质量压缩对png格式这种图片没有作用,因为png是无损压缩。

2.采样率压缩

            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inSampleSize = 2;
            bm = BitmapFactory.decodeFile(Environment
                    .getExternalStorageDirectory().getAbsolutePath()
                    + "/Camera/test.jpg", options);

            Log.i("pic", "压缩后图片的大小" + (bm.getByteCount() / 1024 / 1024)
                    + "M宽度为" + bm.getWidth() + "高度为" + bm.getHeight());

采样率压缩其原理是缩放bitamp的尺寸,通过调节其inSampleSize参数,比如调节为2,宽高会为原来的1/2,内存变回原来的1/4.

3.缩放法压缩(martix)

            Matrix matrix = new Matrix();
            matrix.setScale(0.5f, 0.5f);
            bm = Bitmap.createBitmap(bit, 0, 0, bit.getWidth(),
                    bit.getHeight(), matrix, true);

            Log.i("pic", "压缩后图片的大小" + (bm.getByteCount() / 1024 / 1024)
                    + "M宽度为" + bm.getWidth() + "高度为" + bm.getHeight());

放缩法压缩使用的是通过矩阵对图片进行裁剪,也是通过缩放图片尺寸,来达到压缩图片的效果,和采样率的原理一样。

4.RGB_565压缩

            BitmapFactory.Options options2 = new BitmapFactory.Options();
            options2.inPreferredConfig = Bitmap.Config.RGB_565;

            bm = BitmapFactory.decodeFile(Environment
                    .getExternalStorageDirectory().getAbsolutePath()
                    + "/Camera/test.jpg", options2);

            Log.i("pic", "压缩后图片的大小" + (bm.getByteCount() / 1024 / 1024)
                    + "M宽度为" + bm.getWidth() + "高度为" + bm.getHeight());

RGB_565压缩是通过改用内存占用更小的编码格式来达到压缩的效果。Android默认的颜色模式为ARGB_8888,这个颜色模式色彩最细腻,显示质量最高。一般不建议使用ARGB_4444,因为画质实在是辣鸡,如果对透明度没有要求,建议可以改成RGB_565,相比ARGB_8888将节省一半的内存开销。

5.createScaledBitmap

            bm = Bitmap.createScaledBitmap(bit, 150, 150, true);

            Log.i("pic", "压缩后图片的大小" + (bm.getByteCount() / 1024) + "KB宽度为"
                    + bm.getWidth() + "高度为" + bm.getHeight());

这里是将图片压缩成用户所期望的长度和宽度,但是这里要说,如果用户期望的长度和宽度和原图长度宽度相差太多的话,图片会很不清晰。

此方法与第3种方法实现方式类似,其源码如下:

扫描二维码关注公众号,回复: 10288525 查看本文章
public static Bitmap createScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter) { 
  Matrix m; 
  synchronized (Bitmap.class) { // small pool of just 1 matrix 
  m = sScaleMatrix; 
  sScaleMatrix = null;
 }
  if (m == null) { 
    m = new Matrix(); 
  }
  final int width = src.getWidth(); 
  final int height = src.getHeight(); 
  final float sx = dstWidth / (float)width; 
  final float sy = dstHeight / (float)height; m.setScale(sx, sy); 
  Bitmap b = Bitmap.createBitmap(src, 0, 0, width, height, m, filter);
   synchronized (Bitmap.class) { // do we need to check for null? why not just assign everytime?
  if (sScaleMatrix == null) {
    sScaleMatrix = m; 
    } 
  }
   return b;
}

以上就是5种图片压缩的方法,这里需要强调,他们的压缩仅仅只是对Android中的Bitmap来说的。如果将这些压缩后的Bitmap另存为sd中,他们的内存大小并不一样。

Android手机中,图片的所占的内存大小和很多因素相关,计算起来也很麻烦。为了计算出一个图片的内存大小,可以将图片当做一个文件来间接计算,用如下的方法:

          File file = new File(Environment.getExternalStorageDirectory()
         .getAbsolutePath() + "/Camera/test.jpg");

         Log.i("pic", "file.length()=" + file.length() / 1024);

或者

        FileInputStream fis = null;
        try {
            fis = new FileInputStream(file);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        try {
            Log.i("pic", "fis.available()=" + fis.available() / 1024);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

上面两个方法计算的结果是一样的。

五、总结
  Bitmap所占用的内存 = 图片长度 x 图片宽度 x 一个像素点占用的字节数。3个参数,任意减少一个的值,都能达到了压缩的效果,只是效果不尽相同。
  另外我们这里的介绍的压缩方法只是针对在运行加载的bitmap占用内存的大小。我们在做App内存优化的时候,一般可以从这两个方面入手,一个内存泄漏,另外一个是Bitmap压缩了,在要求像素不高的情况下,可以对Bitmap进行压缩,并且针对一些只使用一次的Bitmap,要做好recycle的处理。

发布了105 篇原创文章 · 获赞 56 · 访问量 22万+

猜你喜欢

转载自blog.csdn.net/suwenlai/article/details/105138792