美文网首页图片处理Android开发Android技术知识
轻量级、无耦合、高灵活图片压缩类库

轻量级、无耦合、高灵活图片压缩类库

作者: af83084249b7 | 来源:发表于2018-04-12 22:03 被阅读555次

起因

各位安卓开发的小伙伴,开发app或多或少都涉及到图片。我们的图片是那里来的呢?毫无疑问,第一次是从网络下载,之后才是用的缓存。
如果图片过于高清(资源大),我们下载展示出来就会很慢,这样用户体验肯定很差。那么,该怎么办呢?对的,下载尺寸更小的图片。不过,这些图片(头像,封面)等都是用户主动上传的。我们需要在上传前就对这些图片进行处理。

思路

我们图片处理的库应该是可以选择配置的。具体有:大小、格式、色彩值、质量等等。这样,我们可以根据业务需求设置不同参数,达到预期效果。

效果(简书让效果图色彩失真了,原图很直观,可参考文章最后github的展示图效果)

流程

首先设置我们想要的参数
    //可以构造者方式设置,也可以创建对象设置属性值
    TCompress tCompress = new TCompress.Builder()
            .setMaxWidth(800)  //指定最大宽度(算法处理之后,实际宽度小于以及接近指定最大宽度)
            .setMaxHeight(900)  //指定最大高度(算法处理之后,实际宽度小于以及接近指定最大高度)
            .setQuality(80)  //指定图片质量(范围0-100,对PNG类型无效)
            .setFormat(Bitmap.CompressFormat.JPEG)  //图片压缩类型,PNG带透明度
            .setConfig(Bitmap.Config.RGB_565) //颜色格式
            .build();
    //    TCompress tCompress = new TCompress();
    //    tCompress.setConfig(Bitmap.Config.RGB_565);
    //    tCompress.setFormat(Bitmap.CompressFormat.WEBP);
    //    tCompress.setQuality(80);
    //    tCompress.setMaxWidth(800);
    //    tCompress.setMaxHeight(800);
选择同步、异步压缩

图片压缩涉及IO耗时操作,推荐异步处理。

//同步压缩
private void sync(TCompress tCompress) {
    File compressedFile = tCompress.compressedToFile(mFile);
    if (compressedFile == null) {
        //请查看文件权限问题(其他问题基本不存在,可以查看日志详情)
        return;
    }
  // 另外三种(设定入参类型、返回类型)
  //  File compressedFile1 = tCompress.compressedToFile(mBitmap);
  //  Bitmap bitmap = tCompress.compressedToBitmap(mFile);
  //  Bitmap bitmap1 = tCompress.compressedToBitmap(mBitmap);

    //数据显示
    showData(compressedFile);
}

------------------------------------------分割--------------------------------------------------

 //异步压缩(入参是文件类型(待压缩图片文件),返回类型是文件(压缩后图片文件))
 private void async(TCompress tCompress) {
    //泛型设置回调类型。如果不指定泛型,也可以根据方法名的ToFile、ToBitmap进行强转
    //文件压缩到指定文件
    tCompress.compressToFileAsync(mFile, new OnCompressListener<File>() {
        //onCompressStart是非抽象方法,可选监听 可以开启提示框等 默认不重写
        @Override
        public void onCompressStart() {
       //     showToast("开始压缩");
        }

        @Override
        public void onCompressFinish(boolean success, File file) {
            if (success) {
                showData(file);
            } else {
                //请查看文件权限问题(其他问题基本不存在,可以查看日志详情)
            }
        }
    });
    //----------------其他三种异步压缩类似(入参、返回参数类型)-------------
   //   otherThreeAsync();
 }

完毕了,哈哈哈。不过这个类库是我自己封装的,最后放地址。

核心实现

主要流程(以图片文件压缩到图片文件为例子,其他过程包含在内)
一:创建tcompress对象指定配置信息(最大宽高参数指定图片同比例压缩后最接近的宽高值,但是实际宽高都小于设定值)。
二:选择压缩模式(输入类型、输出类型。被压缩对象可以是bitmap、文件类型,压缩后的对象也可以是bitmap或者文件类型,根据业务需要选择)
三:一次采样得到图片的宽高,不读入图片数据到内存(防止数据过大)
四:根据图片实际宽高和用户创建tcompress时候指定的最大宽高,计算第一次压缩倍数(让第一次压缩后读入内存的数据略大于用户设定的宽高)
五:根据第一次压缩倍数,设定参数。读入图片数据到内存,得到bitmap对象(此时完成一次压缩)
六:根据函数得到图片的旋转角度(三星手机相册选择图片旋转问题,其他手机正常),进行bitmap的旋转校正处理。
七:根据第一次图片压缩后的宽高(此时略大于用户设定的最大宽高值),以及用户设定的最大宽高值数据,进行算法处理。得到二次压缩比例。
八:根据比例,进行第二次图片压缩,得到最终bitmap。该bitmap宽高都略小于并接近用户设定的最大宽高。
九:根据用户设定的质量、格式等数据,从最终的bitmap得到最终的图片文件。
核心代码(附注释,不懂留言)
 //压缩处理者对象
 public class TCompress {
//默认属性,通过构造者模式或者set方法设置
private int mQuality = 80;
private float mMaxHeight = 1280;
private float mMaxWidth = 960;
private Bitmap.CompressFormat mFormat = Bitmap.CompressFormat.JPEG;
private Bitmap.Config mConfig = Bitmap.Config.ARGB_8888;

//---------------------主线路:文件里面的图片压缩完毕存入文件---------------------------------

   public File compressedToFile(File file) {
    File ret = null;
    try {
        //从图片文件里面获取bitmap对象。
        Bitmap bitmap = getBitmap(file);
        //进行图片的压缩,得到压缩后的bitmap(可以展示给用户了)
        Bitmap compressedBitmap = compressedToBitmap(bitmap);
        //将压缩后的bitmap存入文件(需要时候,将文件上传服务器)
        ret = bitmap2File(compressedBitmap);
        //bitmap资源回收
        bitmap.recycle();
        compressedBitmap.recycle();
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
    return ret;
}

private Bitmap getBitmap(File file) {
    //获取bitmapFactory.options对象(包含宽高等数据,但是并未载入内存,一次采样)
    BitmapFactory.Options options = getOptions(file);
    //根据原始图片文件以及options对象解析bitmap(二次采样,读入实际数据至内存)
    Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath(), options);
    //图片角度处理(我不会告诉你三星拍照后图片需要进行角度调整,要不然是横向图片)
    bitmap = rotateBitmap(bitmap, file);
    return bitmap;
}

private BitmapFactory.Options getOptions(File file) {
    BitmapFactory.Options options = new BitmapFactory.Options();
    //指明,只是解析Bounds数据,不读入数据
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(file.getAbsolutePath(), options);
     //设定第一次压缩的倍数(根据图片实际尺寸和用户设定最大宽高尺寸,算法得出,后续还有第二次压缩)
    options.inSampleSize = setSampleSize(options.outWidth, options.outHeight);
    options.inJustDecodeBounds = false;
    return options;
}

private int setSampleSize(int outWidth, int outHeight) {
   
    int sampleSize = 1;
    //一次缩放倍数处理(保证图片最终尺寸刚好大于设定最大宽高尺寸)
    //之后会进行二次缩放处理
    //第一次是整数倍数处理,尽可能小的尺寸加载图片,占用尽可能少的内存
   while (outWidth > mMaxWidth * (sampleSize + 1) && outHeight > mMaxHeight * (sampleSize + 1)) {
        sampleSize++;
    }
    return sampleSize;
}

private Bitmap rotateBitmap(Bitmap bitmap, File file) {
    //degree等于0,表示图片不需要旋转
    int degree = getPictureDegree(file);
    if (degree == 0) {
        return bitmap;
    }
    Matrix matrix = new Matrix();
    //创建bitmap
    Bitmap ret = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
    //以bitmap的中心为圆心,旋转degree度数(三星角度问题)
    matrix.setRotate(degree, ret.getWidth() / 2, ret.getHeight() / 2);
    Canvas canvas = new Canvas(ret);
    canvas.drawBitmap(bitmap, matrix, null);
    return ret;
}

private static int getPictureDegree(File file) {
    int degree = 0;
    try {
        //获取当前角度
        ExifInterface exifInterface = new ExifInterface(file.getAbsolutePath());
        int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
        switch (orientation) {
            case ExifInterface.ORIENTATION_ROTATE_90:
                degree = 90;
                break;
            case ExifInterface.ORIENTATION_ROTATE_180:
                degree = 180;
                break;
            case ExifInterface.ORIENTATION_ROTATE_270:
                degree = 270;
                break;
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    return degree;
}

//bitmap到压缩后的bitmap
public Bitmap compressedToBitmap(Bitmap bitmap) {
    Bitmap ret = null;
    try {
        float height = bitmap.getHeight();
        float width = bitmap.getWidth();
         //获取压缩(缩放)比例
        float ratio = setRatio(width, height);
         //进行第二次压缩
        ret = Bitmap.createBitmap((int) (width * ratio), (int) (height * ratio), mConfig);
        Canvas canvas = new Canvas(ret);
        canvas.drawBitmap(bitmap, null, new RectF(0, 0, ret.getWidth(), ret.getHeight()), null);
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
    return ret;
}

private float setRatio(float width, float height) {
    float ratio = 1;
    //保证压缩后图片宽高都小于设定的最大宽高值,并且接近之
    if (mMaxWidth < width && mMaxHeight < height) {
        if (mMaxWidth / width < mMaxHeight / height)
            ratio = mMaxWidth / width;
        else ratio = mMaxHeight / height;
    } else if (mMaxWidth < width) ratio = mMaxWidth / width;
    else if (mMaxHeight < height) ratio = mMaxHeight / height;
    return ratio;
}

private File bitmap2File(Bitmap bitmap) {
    File ret = null;
    try {
        //指定文件格式
        String prefix = String.valueOf(System.currentTimeMillis());
        String suffix = null;
        switch (mFormat) {
            case JPEG:
                suffix = ".jpg";
                break;
            case PNG:
                suffix = ".png";
                break;
            case WEBP:
                suffix = ".webp";
                break;
        }
        //创建文件并将bitmap数据写到文件
        ret = File.createTempFile(prefix, suffix);
        ret.deleteOnExit();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        FileOutputStream outputStream = new FileOutputStream(ret);
        //指定了bitmap的格式、质量,最后输给字节流对象
        bitmap.compress(mFormat, mQuality, baos);
        outputStream.write(baos.toByteArray());
        outputStream.flush();
        baos.close();
        outputStream.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return ret;
}

//-------扩展(bitmap到压缩后的bitmap,已经包含在,文件到压缩后的文件的步骤之中)----------------------------------
//文件图片压缩到新的bitmap
public Bitmap compressedToBitmap(File file) {
    Bitmap ret = null;
    try {
        Bitmap bitmap = getBitmap(file);
        ret = compressedToBitmap(bitmap);
        bitmap.recycle();
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
    return ret;
}

//bitmap压缩到新的文件
public File compressedToFile(Bitmap bitmap) {
    File ret = null;
    try {
        Bitmap compressedBitmap = compressedToBitmap(bitmap);
        ret = bitmap2File(compressedBitmap);
        compressedBitmap.recycle();
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
    return ret;
}


//--------------------------------设置参数--------------------------------------


public void setMaxHeight(int maxHeight) {
    mMaxHeight = maxHeight;
}

public void setMaxWidth(int maxWidth) {
    mMaxWidth = maxWidth;
}

public void setQuality(int quality) {
    mQuality = quality;
}

public void setConfig(Bitmap.Config config) {
    mConfig = config;
}

public void setFormat(Bitmap.CompressFormat format) {
    mFormat = format;
}

//---------------------------------构建者模式---------------------------------------
public static class Builder {
    private TCompress mTCompress;

    public Builder() {
        mTCompress = new TCompress();
    }

    public Builder setMaxHeight(int height) {
        mTCompress.mMaxHeight = height;
        return this;
    }

    public Builder setMaxWidth(int weight) {
        mTCompress.mMaxWidth = weight;
        return this;
    }

    public Builder setQuality(int quality) {
        mTCompress.mQuality = quality;
        return this;
    }

    public Builder setConfig(Bitmap.Config config) {
        mTCompress.mConfig = config;
        return this;
    }

    public Builder setFormat(Bitmap.CompressFormat format) {
        mTCompress.mFormat = format;
        return this;
    }

    public TCompress build() {
        return mTCompress;
    }
}

//-------------------------------添加异步处理----------------------------------------------
//图片压缩是耗时的,异步处理比较合适。
//添加属性
public OnCompressListener mListener;
public Handler mHandler;


//--------------------------------Handler--------------------------------------------------

private Handler getHandler() {
    return new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            if (mListener != null) {
                if (msg.obj == null) mListener.onCompressFinish(false, null);
                else mListener.onCompressFinish(true, msg.obj);
            }
            return true;
        }
    });
}

//-------------------------------------异步方法-------------------------------------------

//文件压缩到文件
public void compressToFileAsync(final File file,  OnCompressListener listener) {
    mListener = listener;
    mHandler = getHandler();
    listener.onCompressStart();
    new Thread(new Runnable() {
        @Override
        public void run() {
            File target = compressedToFile(file);
            Message message = mHandler.obtainMessage();
            message.obj = target;
            message.sendToTarget();
        }
    }).start();

}

//Bitmap压缩到文件
public void compressToFileAsync(final Bitmap bitmap,  OnCompressListener listener) {
    mListener = listener;
    mHandler = getHandler();
    listener.onCompressStart();
    new Thread(new Runnable() {
        @Override
        public void run() {
            File target = compressedToFile(bitmap);
            Message message = mHandler.obtainMessage();
            message.obj = target;
            message.sendToTarget();
        }
    }).start();
}

//文件压缩到Bitmap
public void compressToBitmapAsync(final File file, OnCompressListener listener) {
    mListener = listener;
    mHandler = getHandler();
    listener.onCompressStart();
    new Thread(new Runnable() {
        @Override
        public void run() {
            Bitmap target = compressedToBitmap(file);
            Message message = mHandler.obtainMessage();
            message.obj = target;
            message.sendToTarget();
        }
    }).start();
}

//Bitmap压缩到Bitmap
public void compressToBitmapAsync(final Bitmap bitmap, OnCompressListener listener) {
    mListener = listener;
    mHandler = getHandler();
    listener.onCompressStart();
    new Thread(new Runnable() {
        @Override
        public void run() {
            Bitmap target = compressedToBitmap(bitmap);
            Message message = mHandler.obtainMessage();
            message.obj = target;
            message.sendToTarget();
        }
    }).start();
  }
  }

完事。

总结

自认为该仓库轻量级、代码规范易懂、无耦合(不含其他三方库)、灵活易用,是个良心图片处理类库。希望能帮助到大家~

依赖:compile 'com.jkt:tcompress:1.2.3'

地址:https://github.com/HoldMyOwn/TCompress

相关文章

网友评论

本文标题:轻量级、无耦合、高灵活图片压缩类库

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