主要記錄兩件事,1.高斯模糊毛玻璃效果;2.圖片水印的添加
1.高斯模糊
什麼是高斯模糊,高斯模糊(英語:Gaussian Blur),也叫高斯平滑,是在Adobe Photoshop、GIMP以及Paint.NET等圖像處理軟件中廣泛使用的處理效果,通常用它來減少圖像噪聲以及降低細節層次。這種模糊技術生成的圖像,其視覺效果就像是經過一個半透明屏幕在觀察圖像,這與鏡頭焦外成像效果散景以及普通照明陰影中的效果都明顯不同。看不懂?我也看不懂,來看下成品gif看下吧
1.1靜態高斯模糊
沒錯,就是毛玻璃,就是模糊,常見的就是網易雲音樂播放的後面的那個背景。我們使用使用官方提供在Support Library中的一個工具來做,就是RenderScript,這玩意使用C99衍生語言進行腳本編寫的,相較於Java性能是大大的提升,Google官方也已經給出了對應的解決方案,我們並不需要編寫對應的腳本就可以使用了。
先不說動態的,我們先實現一個靜態的模糊一張圖。我們需要兩個變量,一個是Bitmap對象,另一個就是模糊半徑,模糊半徑的範圍在0-25,超過範圍會報錯,數值越大代表模糊程度越高。我們對其封裝成一個方法,如下
private Bitmap blur(Bitmap bitmap, float radius) {
Bitmap output = Bitmap.createBitmap(bitmap); // 創建輸出圖片
RenderScript rs = RenderScript.create(this); // 構建一個RenderScript對象
ScriptIntrinsicBlur gaussianBlue = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)); // 創建高斯模糊腳本
Allocation allIn = Allocation.createFromBitmap(rs, bitmap); // 創建用於輸入的腳本類型
Allocation allOut = Allocation.createFromBitmap(rs, output); // 創建用於輸出的腳本類型
gaussianBlue.setRadius(radius); // 設置模糊半徑,範圍0f<radius<=25f
gaussianBlue.setInput(allIn); // 設置輸入腳本類型
gaussianBlue.forEach(allOut); // 執行高斯模糊算法,並將結果填入輸出腳本類型中
allOut.copyTo(output); // 將輸出內存編碼爲Bitmap,圖片大小必須注意
rs.destroy(); // 關閉RenderScript對象,API>=23則使用rs.releaseAllContexts()
return output;
}
註釋已經很詳細了,也都是固定寫法,CV大法就好,需要注意的就是模糊半徑的範圍是0-25,那如果25的模糊程度達不到你想要的效果怎麼辦,最優的解就是先壓縮bitmap,然後再模糊,這樣大大減小算法的速度。錯誤的寫法就是循環模糊幾次,這樣只會徒增模糊算法的調用時長,代碼簡單,不再演示。然後我們將處理後得到的bitmap設置給ImageView就搞定了。
Bitmap srcBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.pic);
Bitmap blurBitmap = blur(srcBitmap, 25);
ivBlur.setImageBitmap(blurBitmap);
1.2動態高斯模糊
如果我們想通過一個滑動條控制圖片的模糊程度,這時候最簡單的辦法就有了,我們根據seekbar的值去改變模糊半徑,然後重新走一遍模糊算法,得到bitmap再去設置給ImageView,但是這有一個很大的弊端,那就是滑動條數值變化很快,而算法一遍一遍的加載這樣會大大增加內存消耗,畢竟算法跑的那麼頻繁,會導致卡頓,於是有了一種更巧妙的辦法,我們先得到一個模糊程度最大的圖,然後在其上面覆蓋一張原圖,當seekbar拖動的時候,我們只需要修改原圖的透明度,就ok啦,原圖完全可見時就是原圖,原圖完全不可見的時候,就是模糊程度最高的時候。代碼很簡單,不作講解,完整代碼如下。
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".BlurActivity">
<ImageView
android:id="@+id/iv_blur"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/iv_src"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/pic" />
<SeekBar
android:id="@+id/sb"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_gravity="bottom"
android:layout_margin="30dp"
android:max="255"
android:min="0" />
</FrameLayout>
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_blur);
ivBlur = findViewById(R.id.iv_blur);
ivSrc = findViewById(R.id.iv_src);
sb = findViewById(R.id.sb);
Bitmap srcBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.pic);
Bitmap blurBitmap = blur(srcBitmap, 25);
ivBlur.setImageBitmap(blurBitmap);
sb.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
ivSrc.setImageAlpha(255 - progress);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
}
private Bitmap blur(Bitmap bitmap, float radius) {
Bitmap output = Bitmap.createBitmap(bitmap); // 創建輸出圖片
RenderScript rs = RenderScript.create(this); // 構建一個RenderScript對象
ScriptIntrinsicBlur gaussianBlue = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)); // 創建高斯模糊腳本
Allocation allIn = Allocation.createFromBitmap(rs, bitmap); // 創建用於輸入的腳本類型
Allocation allOut = Allocation.createFromBitmap(rs, output); // 創建用於輸出的腳本類型
gaussianBlue.setRadius(radius); // 設置模糊半徑,範圍0f<radius<=25f
gaussianBlue.setInput(allIn); // 設置輸入腳本類型
gaussianBlue.forEach(allOut); // 執行高斯模糊算法,並將結果填入輸出腳本類型中
allOut.copyTo(output); // 將輸出內存編碼爲Bitmap,圖片大小必須注意
rs.destroy(); // 關閉RenderScript對象,API>=23則使用rs.releaseAllContexts()
return output;
}
2.水印的實現
先上gif看效果
點擊添加水印按鈕後,會在bitmap右下角生成一個水印,這裏以添加字符串爲例,當然也可以添加其他bitmap,或者其他圖形等等。原理也很簡單,BitmapFactory解析出來的一系列Bitmap是不允許編輯的,所以我們首先利用Bitmap類新建一張空白Bitmap,大小與原來Bitmap大小相同,然後爲這個空白Bitmap創建一個Canvas,首先繪製原來的Bitmap,然後繪製字符串,如果你的水印也是一個bitmap,那也可以再繪製一張bitmap。我們將整個添加水印抽成一個方法
private Bitmap addWatermark(Bitmap srcBitmap, String watermark) {
Paint paint = new Paint();
paint.setColor(Color.WHITE);
paint.setTextSize(50);
Bitmap resultBitmap = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(resultBitmap);
canvas.drawBitmap(srcBitmap, 0, 0, null);
float watermarkWidth = paint.measureText(watermark);
canvas.drawText(watermark, srcBitmap.getWidth() - watermarkWidth - 20, srcBitmap.getHeight() - 30, paint);
return resultBitmap;
}
當我們點擊按鈕後就去生成帶水印的Bitmap,設置給Bitmap即可,如果想要保存Bitmap,可以使用compress保存圖片,注意保存的格式,JPG是不支持透明度的有損格式,PNG是無損且帶透明度的格式,根據具體需求保存不同的格式。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_watermark);
ivWatermark = findViewById(R.id.iv_watermark);
final EditText etWatermark = findViewById(R.id.et_watermark);
Button btWatermark = findViewById(R.id.bt_watermark);
srcBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.pic);
Bitmap watermarkBitmap = addWatermark(srcBitmap, "水印");
ivWatermark.setImageBitmap(watermarkBitmap);
btWatermark.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String string = etWatermark.getText().toString();
Bitmap watermarkBitmap = addWatermark(srcBitmap, string);
ivWatermark.setImageBitmap(watermarkBitmap);
}
});
}