GPUImage图片滤镜处理的第三方开源库,对照IOS版的GPUImage写的,部分功能尚未完善,目前也有很多种滤镜,常用的滤镜基本上都有,请先浏览一遍github上面的用法。

依赖的库

repositories {
    jcenter()
}

dependencies {
//这个版本号2.x.x,具体的数字看,github官方说明
//README.md下方Download后面的版本号
    implementation 'jp.co.cyberagent.android:gpuimage:2.x.x'
}

注意事项

一、图片变形

  Android版目前没有IOS那么多类,那么多用法,我们用的最多的就是GPUImageView这个自定义view,继承的FrameLayout,并不是继承的ImageView,所以,它这里显示图片的时候,会有图片变形的问题。我的***处理方法:***

  1. 先用Glide获取图片的宽高
  2. 然后获取GPUImageView的LayoutParams,动态设置控件的宽高

二、内存溢出

  1. 图片过大造成的内存溢出,压缩图片,推荐使用鲁班压缩
  2. 频繁使用GPUImage获取Bitmap的getBitmapWithFilterApplied()方法,造成Bitmap过多的内存泄漏,推荐用WeakReference(弱引用)标记Bitmap,GC自动回收
  3. 显示大图和缩略图,一般都是一个大图和多种添加滤镜后的效果图(这个是缩略图),这里缩略图再通过getBitmapWithFilterApplied获取之前,最好吧原图按照规则缩小之后再获取显示,这样也能尽可能的减少内存的占用,点击缩略图显示大的效果图的时候,并不是改变bitmap,是给gpuIamgeView对象设置你点击目标图使用滤镜即可,这样也可以避免内存过多的消耗

三、滤镜添加

  1. 单一滤镜的添加
//这里以添加黑白滤镜为例
GPUImageView gpuImageView = findViewById(R.id.img);
gpuImageView.setImage(bitmap);
gpuImageView.setFilter(new GPUImageGrayscaleFilter());
  1. 组合滤镜的添加GPUImageFilterGroup
GPUImageView gpuImageView = findViewById(R.id.img);
gpuImageView.setImage(bitmap);
GPUImageFilterGroup filterGroup = new GPUImageFilterGroup();
//把你需要添加的滤镜放到GPUImageFilterGroup容器里面,
//这里我添加了灰色滤镜,曝光度滤镜和饱和度滤镜理论上可以添加无数个
filterGroup.add(new GPUImageGrayscaleFilter());
filterGroup.add(new GPUImageExposureFilter());
filterGroup.add(new GPUImageSaturationFilter());
//把这个容器添加到GPUImageView
gpuImageView.setFilter(filterGroup);
  1. 多张图片的滤镜
//这里以GPUImageTwoInputFilter为例(可以加到组合滤镜里面),它有多个子类
//我们这里用GPUImageChromaKeyBlendFilter为例
//实现的效果是一个过渡效果,从原图过渡到目标图
GPUImageView gpuImageView = findViewById(R.id.img);
//设置原图
gpuImageView.setImage(bitmap);
//新建滤镜对象,并且把目标图设置给滤镜
GPUImageChromaKeyBlendFilter keyBlendFilter = new GPUImageChromaKeyBlendFilter();
//设置目标图
keyBlendFilter.setBitmap(bitmap1);
//平滑的过渡方法,改变参数的值即可
keyBlendFilter.setSmoothing(progressFloat);
//把滤镜设置给GPUImageView
gpuImageView.setFilter(keyBlendFilter);

四、微调(敏感度问题)

  只要构造方法,方法带参数的,都可以微调,这里微调的取值范围,Filter源码的类注释上面都有。如果,我们把seekBar的取值范围设置成类注释上面的范围,你滑动很小距离的seekBar,图片变化就会很大,所以,我们一般都是缩小范围再使用。

//第一个参数seekBar是最大值,第二个参数是最小值,第三个参数是默认值,第四个参数是seekbar分几段
 //mapSeekBarBean.put(TYPE_SATURATION, new SeekBarBean(2, 0, 0.5f, 10));
 mapSeekBarBean.put(TYPE_SATURATION, new SeekBarBean(100, 0, 50f, 2));
 //mapSeekBarBean.put(TYPE_BRIGHTNESS, new SeekBarBean(1, -1, 0.5f, 10));
 mapSeekBarBean.put(TYPE_BRIGHTNESS, new SeekBarBean(100, 0, 50f, 2));
 //mapSeekBarBean.put(TYPE_EXPOSURE, new SeekBarBean(10, -10, 0.5f, 0));
 mapSeekBarBean.put(TYPE_EXPOSURE, new SeekBarBean(100, 0, 50f, 2));
 //mapSeekBarBean.put(TYPE_CONTRAST, new SeekBarBean(4, 0, 0.25f, 0));
 mapSeekBarBean.put(TYPE_CONTRAST, new SeekBarBean(100, 0, 25f, 2));
 mapSeekBarBean.put(TYPE_POSTERIZE, new SeekBarBean(256, 0, 100f, 3));
 //mapSeekBarBean.put(TYPE_HIGH_LIGHT_SHADOW, new SeekBarBean(1, 0, 0f, 0));
 mapSeekBarBean.put(TYPE_HIGH_LIGHT_SHADOW, new SeekBarBean(100, 0, 0f, 2));
 mapSeekBarBean.put(TYPE_SHARPEN, new SeekBarBean(100, 0, 50f, 3));
 //mapSeekBarBean.put(TYPE_GAMMA, new SeekBarBean(3, 0, 0.33f, 0));
 mapSeekBarBean.put(TYPE_GAMMA, new SeekBarBean(100, 0, 33f, 3));
 //mapSeekBarBean.put(TYPE_OPACITY, new SeekBarBean(1, 0, 1f, 0));
 mapSeekBarBean.put(TYPE_OPACITY, new SeekBarBean(100, 0, 100f, 2));
 //mapSeekBarBean.put(TYPE_VIBRANCE, new SeekBarBean(1, 0, 0f, 0));
 mapSeekBarBean.put(TYPE_VIBRANCE, new SeekBarBean(100, 0, 0f, 2));
//这里是最终设置的值
switch (entrySet.getKey()) {
                case TYPE_SATURATION:
                    //最后面*2是范围(0,2)
                    float f1 = (entrySet.getValue().getProgress() / entrySet.getValue().getMax()) * 2;
                    filters.add(new GPUImageSaturationFilter(f1));
                    break;
                case TYPE_BRIGHTNESS:
                    float f2 = entrySet.getValue().getProgress();
                    if (f2 == 50) {
                        f2 = 0f;
                    } else {
                        //后面的*0.7是范围(-1,1),以中间0为准,分成两部分(-1,0),(0,1)
                        //负数为变暗,正数为变亮,本应该*1
                        f2 = (float) (((f2 - 50) / 50) * 0.4);
                    }
                    filters.add(new GPUImageBrightnessFilter(f2));
                    break;
                case TYPE_EXPOSURE:
                    float f3 = entrySet.getValue().getProgress();
                    if (f3 == 50) {
                        f3 = 0f;
                    } else {
                        //后面的*1是范围(-10,10),以中间0为准,分成两部分(-10,0),(0,10)
                        //负数为变暗,正数为变亮,本应该*10
                        f3 = ((f3 - 50) / 50) * 1;
                    }
                    filters.add(new GPUImageExposureFilter(f3));
                    break;
                case TYPE_CONTRAST:
                    //最后面*4是范围(0,4)
                    float f4 = (entrySet.getValue().getProgress() / entrySet.getValue().getMax()) * 4;
                    filters.add(new GPUImageContrastFilter(f4));
                    break;
                case TYPE_POSTERIZE:
                    filters.add(new GPUImagePosterizeFilter((int) entrySet.getValue().getProgress()));
                    break;
                case TYPE_HIGH_LIGHT_SHADOW:
                    GPUImageHighlightShadowFilter highlightShadowFilter = new GPUImageHighlightShadowFilter();
                    float f9 = (entrySet.getValue().getProgress() / entrySet.getValue().getMax()) * 1;
                    highlightShadowFilter.setHighlights(1 - f9);
                    highlightShadowFilter.setShadows(f9);
                    filters.add(highlightShadowFilter);
                    break;
                case TYPE_SHARPEN:
                    float f5 = entrySet.getValue().getProgress();
                    if (f5 == 50) {
                        f5 = 0f;
                    } else {
                        //后面的*4是范围(-4,4),以中间0为准,分成两部分(-4,0),(0,4)
                        //负数为变暗,正数为变亮,本应该*4
                        f5 = ((f5 - 50) / 50) * 4;
                    }
                    filters.add(new GPUImageSharpenFilter(f5));
                    break;
                case TYPE_GAMMA:
                    //最后面*3是范围(0,3)
                    float f6 = (entrySet.getValue().getProgress() / entrySet.getValue().getMax()) * 3;
                    filters.add(new GPUImageGammaFilter(f6));
                    break;
                case TYPE_OPACITY:
                    float f7 = (entrySet.getValue().getProgress() / entrySet.getValue().getMax()) * 1;
                    filters.add(new GPUImageOpacityFilter(f7));
                    break;
                case TYPE_VIBRANCE:
                    float f8 = (entrySet.getValue().getProgress() / entrySet.getValue().getMax()) * 1;
                    filters.add(new GPUImageVibranceFilter(f8));
                    break;
                default:
            }

用法

  上面的注意事项里面已经说了简单的用法了,怎么获取滤镜后的图片呢?

//这个方法是获取bitmap对象,至于怎么保存,那就是你自己做了
gpuImageView.getGPUImage().getBitmapWithFilterApplied();

//当然,库也提供了保存图片的方法:保存的文件夹名称,文件名字,回调方法
gpuImageView.saveToPictures(folderName,fileName,OnPictureSavedListener)
//其中回调方法里面返回的uri,不能直接传给File,会找不到路径,需要转换一下
/**
     * 根据Uri获取文件的路径
     *
     * @param context    context
     * @param contentURI uri
     * @return 文件路径
     */
    public static String getRealPathFromURI(Context context, Uri contentURI) {
        String result;
        Cursor cursor = context.getContentResolver().query(contentURI,
                new String[]{MediaStore.Images.ImageColumns.DATA},
                null, null, null);
        if (cursor == null) {
            result = contentURI.getPath();
        } else {
            cursor.moveToFirst();
            int index = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
            result = cursor.getString(index);
            cursor.close();
        }
        return result;
    }

项目就不贴出来了。