首先要清楚为什么要对图片进行压缩,对于移动设备来说,硬件(CPU、内存、磁盘等)资源有限,图片放在内存中要占据大量的内存,放在磁盘中也要占据较大的空间,所以在不影响用户体验的条件下,我们要尽可能的把图片进行压缩,以节省有限的硬件资源。下面我们分析一下常用的几种压缩方式,以及在什么情况下使用。

目录

一、质量压缩

1 format

2 quality

3 stream

二、采样率压缩

三、按比例压缩


一、质量压缩

质量压缩的特点是图片文件大小会减小,但是图片的像素数不会改变,加载压缩后的图片,占据的内存不会减少。质量压缩依靠Bitmap. compress(CompressFormat format, int quality, OutputStream stream)实现

* @param format   The format of the compressed image
     * @param quality  Hint to the compressor, 0-100. 0 meaning compress for
     *                 small size, 100 meaning compress for max quality. Some
     *                 formats, like PNG which is lossless, will ignore the
     *                 quality setting
     * @param stream   The outputstream to write the compressed data.
     * @return true if successfully compressed to the specified stream.
     */
    public boolean compress(CompressFormat format, int quality, OutputStream stream) {
        checkRecycled("Can't compress a recycled bitmap");
        // do explicit check before calling the native method
        if (stream == null) {
            throw new NullPointerException();
        }
        if (quality < 0 || quality > 100) {
            throw new IllegalArgumentException("quality must be 0..100");
        }
        Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "Bitmap.compress");
        boolean result = nativeCompress(mNativePtr, format.nativeInt,
                quality, stream, new byte[WORKING_COMPRESS_STORAGE]);
        Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
        return result;
    }

1 format

format表示以某种格式压缩图片,支持三种格式:Bitmap.CompressFormat.JPEG、Bitmap.CompressFormat.PNG、Bitmap.CompressFormat.WEBP。注意Bitmap.CompressFormat.XXX要和文件格式一致。

//生成png格式图片
String fileName = "test.png";
File file = new File(appDir, fileName);
bitmap.compress(Bitmap.CompressFormat.PNG, compressRatio, baos);


//生成jpeg格式图片
String fileName = "test.jpeg";
File file = new File(appDir, fileName);
bitmap.compress(Bitmap.CompressFormat.JPEG, compressRatio, baos);

2 quality

quality代表压缩程度,取值0-100。100表示不压缩,压缩后图片和原图片文件大小一样;0表示压缩到最小的图片文件大小,质量压缩是保持像素不变的前提下改变图片的位深及透明度,来压缩图片大小,因为要保持像素不变,所以它就无法无限压缩,图片文件大小到达一个值之后就不会继续变小了。如下,原图是png,压缩成jpeg,quality依次设置为100、50、10、5、2、1、0,当quality=2时已经压缩到极限,quality再减小,图片大小不在减小,并且随着图片质量的压缩,图片越来越模糊。

Compressor android 压缩 安卓 压缩_宽高



quality为100、10、2时:

Compressor android 压缩 安卓 压缩_采样率_02

Compressor android 压缩 安卓 压缩_bitmap 图片 压缩_03

Compressor android 压缩 安卓 压缩_宽高_04




此外,还有一点需要注意,如果format以Bitmap.CompressFormat.PNG格式压缩,如论quality为何值,压缩后图片文件大小都不会变化,也就是quality对Bitmap.CompressFormat.PNG压缩格式不起作用,因为png图片是无损的,不能进行压缩。总结如下:

Compressor android 压缩 安卓 压缩_文件大小_05

3 stream

将压缩的图片写入输出流中,进而保存成文件。

根据上面实验内容,可以知道质量压缩适用于保存图片时,减小图片文件大小,节约磁盘空间,quality选取要适当,不能使图片模糊。

二、采样率压缩

    当ImageView尺寸比图片尺寸过小时,加载原图的话,ImageView不仅不能全部展现出来,而且原图会占据较大的内存,容易出现oom,这时要使用采样率压缩将图片宽高压缩到ImageView的宽高。采样率压缩时图片的宽高按比例压缩,压缩后图片像素数会减少,原图1000*1000,inSampleSize=4,压缩后的图片宽高为250*250,内存只占据原图的1/16,实现过程如下:

/**
     * 采样压缩,图片宽高按比例缩小,像素数减少、图片容量缩小
     * 宽、高压缩比为1/2、1/4/、1/8、1/16
     *
     * @param requiredWidth
     * @param requiredHeight
     */
    private void sampleSizeCompress(int requiredWidth, int requiredHeight) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        //第一次加载,返回null,没有生成bitmap
        BitmapFactory.decodeResource(getResources(), R.drawable.girl, options);
        // 源图片的高度和宽度
        int height = options.outHeight;
        int width = options.outWidth;
        int inSampleSize = 1;
        if (height > requiredHeight || width > requiredWidth) {
            // 计算出实际宽高和目标宽高的比率
            final int heightRatio = Math.round((float) height / (float) requiredHeight);
            final int widthRatio = Math.round((float) width / (float) requiredWidth);
            // 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高
            // 一定都会大于等于目标的宽和高。
            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
            Log.i("图像inSampleSize", inSampleSize + "");
        }
        options.inSampleSize = inSampleSize;
        options.inJustDecodeBounds = false;
        final Bitmap compressBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.girl, options);
        mCompressImg.setImageBitmap(compressBitmap);
        Log.i("压缩后图像素数:", compressBitmap.getWidth() + "*" + compressBitmap.getHeight());
        File appDir = new File(Environment.getExternalStorageDirectory(), "123");
        if (!appDir.exists()) {
            appDir.mkdir();
        }
        SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
        String fileName = df.format(new Date()) + ".jpeg";
        final File file = new File(appDir, fileName);
        new Thread() {
            @Override
            public void run() {
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                // 把压缩后的数据存放到baos中
                compressBitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
                try {
                    FileOutputStream fos = new FileOutputStream(file);
                    fos.write(baos.toByteArray());
                    fos.flush();
                    fos.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }

    采样率压缩,需要两次加载bitmap。第一次加载前,将options.inJustDecodeBounds(只是找到bitmap的边界值)设置为true,第一次加载,返回null,没有生成bitmap,但是可以得到原图的宽高。之后根据ImageView的宽高计算采样率,也就是bitmap要压缩的倍率,inSampleSize可以稍小,不能偏大,inSampleSize稍小时,图片会比ImageView稍大些,这种情况可以接受,但是inSampleSize偏大时,图片会比imageView稍小,此时图片会拉伸,图片质量会下降。此外,inSampleSize只能是2^n,即使计算的值不是2^n,实际压缩时依然是按照2^n压缩。如计算得到inSampleSize=3,实际按照inSampleSize=2压缩,inSampleSize=7,实际按照inSampleSize=4压缩,inSampleSize=9,实际按照inSampleSize=8压缩。下面看实际的例子,ImageView宽高为200*200时:

Compressor android 压缩 安卓 压缩_宽高_06

Compressor android 压缩 安卓 压缩_采样率_07

imagView宽高是200*200,压缩后图片宽高是630*395,比原图缩小了4倍,内存占用为原来的1/16。

imagView宽高是300*300时:

Compressor android 压缩 安卓 压缩_文件大小_08

Compressor android 压缩 安卓 压缩_采样率_09

ImageView宽高是300*300,压缩后的图像宽高1260*788,比原图缩小了2倍,占用内存为原图的1/4。

根据上面的实验内容,当ImageView比图片明显偏小时,为了减少内存占用,防止oom,用采样率压缩压缩图片,降低内存占用。

三、按比例压缩

    按比例压缩过程是:给定压缩图的宽高,根据原图的宽高,计算宽、高压缩比例,按比例压缩。如,原图100*100,要求压缩图为50*25,那么原图的宽压缩2倍、高压缩4倍,能得到压缩图,图片像素数为原图的1/8。

/**
     * 指定宽高进行尺寸压缩,图片按比例缩小,原图像素数: 1260*788,压缩后图像素数::126*78
,宽为原图宽1/10,高为原图高1/10,像素数减少为1/100
     * ,
     * 压缩后的图片文件大小也会减小
     *
     * @param bitmap
     * @param requiredWidth
     * @param requiredHeight
     */
    private void dimenCompress(Bitmap bitmap, int requiredWidth, int requiredHeight) {
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        float scaleWidth = ((float) requiredWidth) / width;
        float scaleHeight = ((float) requiredHeight) / height;
        Matrix matrix = new Matrix();
        matrix.postScale(scaleWidth, scaleHeight);
        final Bitmap compressBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
        Log.i("压缩后图像素数:", compressBitmap.getWidth() + "*" + compressBitmap.getHeight());
        mCompressImg.setImageBitmap(compressBitmap);
        File appDir = new File(Environment.getExternalStorageDirectory(), "123");
        if (!appDir.exists()) {
            appDir.mkdir();
        }
        SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
        String fileName = df.format(new Date()) + ".jpeg";
        final File file = new File(appDir, fileName);
        new Thread() {
            @Override
            public void run() {
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                // 把压缩后的数据存放到baos中
                compressBitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
                try {
                    FileOutputStream fos = new FileOutputStream(file);
                    fos.write(baos.toByteArray());
                    fos.flush();
                    fos.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }.start();

    }

下图分别为宽高压缩1/5、1/5 ; 宽高压缩1/5、1/10:

Compressor android 压缩 安卓 压缩_宽高_10

Compressor android 压缩 安卓 压缩_文件大小_11

Compressor android 压缩 安卓 压缩_宽高_12

Compressor android 压缩 安卓 压缩_宽高_13


图片压缩到这里就总结完了,个人能力有限,希望大家批评指正!