天天看點

圖檔濾鏡——GPUImage

個人部落格: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,是以,它這裡顯示圖檔的時候,會有圖檔變形的問題。我的***處理方法:***

  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;
    }
           

項目就不貼出來了。