个人博客:haichenyi.com。感谢关注
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,所以,它这里显示图片的时候,会有图片变形的问题。我的处理方法:
- 先用Glide获取图片的宽高
- 然后获取GPUImageView的LayoutParams,动态设置控件的宽高
二、内存溢出
- 图片过大造成的内存溢出,压缩图片,推荐使用鲁班压缩
- 频繁使用GPUImage获取Bitmap的getBitmapWithFilterApplied()方法,造成Bitmap过多的内存泄漏,推荐用WeakReference(弱引用)标记Bitmap,GC自动回收
- 显示大图和缩略图,一般都是一个大图和多种添加滤镜后的效果图(这个是缩略图),这里缩略图再通过getBitmapWithFilterApplied获取之前,最好吧原图按照规则缩小之后再获取显示,这样也能尽可能的减少内存的占用,点击缩略图显示大的效果图的时候,并不是改变bitmap,是给gpuIamgeView对象设置你点击目标图使用滤镜即可,这样也可以避免内存过多的消耗
三、滤镜添加
- 单一滤镜的添加
//这里以添加黑白滤镜为例
GPUImageView gpuImageView = findViewById(R.id.img);
gpuImageView.setImage(bitmap);
gpuImageView.setFilter(new GPUImageGrayscaleFilter());
- 组合滤镜的添加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);
- 多张图片的滤镜
//这里以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;
}
项目就不贴出来了。