Android 图片压缩

作者: 1a473fcb13b0 | 来源:发表于2019-01-15 20:50 被阅读129次

项目内res里面的图片可以放入https://tinypng.com/里面压一压,较大的项目随随便便小个10来M。

对于网络加载下来的图片我们可以使用PicassoGlideFresco进行压缩,具体怎么使用百度一下就行了

如果是从本地通过相册或者拍照上传图片的话,则需要通过以下方式进行压缩

一、使用第三方框架

Luban(鲁班)

image.png

使用步骤
1、导入依赖

implementation 'top.zibin:Luban:1.1.8'

2、调用压缩方法
异步调用:Luban内部采用IO线程进行图片压缩,外部调用只需设置好结果监听即可。

Luban.with(this)
        .load(photos)
        .ignoreBy(100)
        .setTargetDir(getPath())
        .filter(new CompressionPredicate() {
          @Override
          public boolean apply(String path) {
            return !(TextUtils.isEmpty(path) || path.toLowerCase().endsWith(".gif"));
          }
        })
        .setCompressListener(new OnCompressListener() {
          @Override
          public void onStart() {
            // TODO 压缩开始前调用,可以在方法内启动 loading UI
          }

          @Override
          public void onSuccess(File file) {
            // TODO 压缩成功后调用,返回压缩后的图片文件
          }

          @Override
          public void onError(Throwable e) {
            // TODO 当压缩过程出现问题时调用
          }
        }).launch();

同步调用:同步方法请尽量避免在主线程调用以免阻塞主线程,下面以rxJava调用为例:

Flowable.just(photos)
    .observeOn(Schedulers.io())
    .map(new Function<List<String>, List<File>>() {
      @Override public List<File> apply(@NonNull List<String> list) throws Exception {
        // 同步方法直接返回压缩后的文件
        return Luban.with(MainActivity.this).load(list).get();
      }
    })
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe();

二、自定义压缩工具类

package com.contoso.facetutorial;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.sql.Date;
import java.text.SimpleDateFormat;
 
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.os.Environment;
 
/**
 * 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种方法实现方式类似,其源码如下:

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的处理。

参考链接:
https://www.jianshu.com/p/3ac26611bc0d
https://blog.csdn.net/HarryWeasley/article/details/51955467
https://www.jianshu.com/p/08ed0e3c4e71

相关文章

  • 图片压缩

    Android应用开发中三种常见的图片压缩方法,分别是:质量压缩法、比例压缩法(根据路径获取图片并压缩)和比例压缩...

  • Android:图片压缩的几种方式

    1、前言 在Android中,图片的压缩对于内存的优化很重要 通过这篇文章对Android中的几种图片压缩方式进行...

  • 图片压缩

    Android图片压缩常用的有质量压缩、尺寸压缩、采样率压缩以及通过JNI调用libjpeg库来进行压缩(尺寸压缩...

  • 【Android开发基础系列】图片专题

    1 图片编辑处理 1.1 图片裁切 转载自:bitmap的六种压缩方式,Android图片压缩 http://bl...

  • 资源图片优化

    tinypng 在线图片压缩 webP android studio选中图片右键Convert to webP

  • Android

    Android常用图片压缩方式 质量压缩 尺寸压缩 1. 质量压缩 质量压缩通过相应算法进行优化Bitmap的位深...

  • bitmap的六种压缩方式,Android图片压缩

    此处分享一个图片的压缩处理方式 : bitmap的六种压缩方式,Android图片压缩 转载链接,点击查看详情 !

  • Android图片压缩

    1.采样率压缩 采样率压缩是改变了图片的像素,他是通过先读取图片的边,然后在自己设定图片的边,然后根据设定,读取图...

  • android图片压缩

    质量压缩 尺寸压缩 缩放法压缩(matrix) RGB_565法(比ARGB_888少一半) createScal...

  • Android图片压缩

    首先,需要注意的是图片在内存中的大小是根据分辨率来决定的,即height*width BitmapFactory这...

网友评论

    本文标题:Android 图片压缩

    本文链接:https://www.haomeiwen.com/subject/tspydqtx.html