在使用知乎,微博的時候,我們經(jīng)常可以看到自己上傳的圖片被加上了文字水印,在實際的應(yīng)用開發(fā)過程中,很多客戶端都需要開發(fā)者自己編寫 Canvs 繪制圖形水印的方法,今天我想在這里介紹一個輕量級的開源 Android 圖片水印框架來避免開發(fā)過程中編寫復(fù)雜的繪圖代碼:AndroidWM,并且剖析它的實現(xiàn)過程和特色用法。
這個框架最大程度上簡化了圖片水印繪制的問題,暴露了靈活的接口給用戶使用。在繪制水印的時候,只需要創(chuàng)建一個水印對象,一個水印創(chuàng)建者對象,然后把定制的水印對象傳給水印創(chuàng)建者即可。
實現(xiàn)水印第一步:引用類庫 androidWM
如果你使用 Android Studio 進行項目的開發(fā),只需要在應(yīng)用 app 的 build.gradle
里面添加上這么一行代碼:
dependencies {
...
implementation 'com.huangyz0918:androidwm:0.2.3'
...
}
然后點擊同步 gradle,讓其自動下載并且安裝好依賴即可。
實現(xiàn)水印第二步:創(chuàng)建一個水印,完工!
androidWM 給我們提供了四種不同的水印:
- 圖形水印
- 文字水印
- 隱形圖形水印
- 隱形文字水印
我們可以創(chuàng)建不同的水印對象來實現(xiàn)對水印風(fēng)格的定制化,比如我想創(chuàng)建一個文字水印,我就實例化一個WatermarkText
,并且設(shè)置它的屬性:
WatermarkText watermarkText = new WatermarkText(“Hello World”)
.setPositionX(0.5) // 橫坐標(biāo)
.setPositionY(0.5) // 縱坐標(biāo)
.setTextAlpha(100) // 透明度
.setTextColor(Color.WHITE) // 文字水印文字顏色
.setTextFont(R.font.champagne) // 字體
.setTextShadow(0.1f, 5, 5, Color.BLUE); // 字體的陰影
這樣我就拿到了一個文字水印 watermarkText
,接下來,我還需要一個畫師幫助我把水印畫到我想要的背景底圖上面,于是我們需要實例化一個WatermarkBuilder
:
WatermarkBuilder.create(this, backgroundBitmap) // 加載背景底圖
.loadWatermarkText(watermarkText) // 加載水印對象
.getWatermark() // 繪制帶有水印的圖片
.setToImageView(backgroundView); // 設(shè)置結(jié)果到 ImageView 里
這里一氣呵成,首先傳入一個上下文 context
,然后傳入一張你想要繪制的背景底圖,這個背景圖片可以來自于一個 ImageView
, 也可以來自于系統(tǒng)的資源(如: R.drawable.image
),你要是想直接傳入一個 Bitmap
也是可以的。最后,在調(diào)用完方法 .getWatermark()
以后,androidWM 可以幫助你直接將帶有水印的圖片設(shè)置到一個ImageView
里面,或者使用方法 .getOutputImage()
獲得輸出的 Bitmap
。
同理,你也可以通過實例化一個 WatermarkImage
來創(chuàng)建一個圖像水印:
WatermarkImage watermarkImage = new WatermarkImage(watermarkBitmap)
.setImageAlpha(80)
.setPositionX(0.5)
.setPositionY(0.5)
.setRotation(15)
.setSize(0.3);
WatermarkBuilder
.create(this, backgroundBitmap)
.loadWatermarkImage(watermarkImage)
.getWatermark()
.setToImageView(backgroundView);
Boom ! 畫出來的水印長這樣:
創(chuàng)建隱形水印
隱形水印是框架 androidWM 的一個特色,它支持兩種不同方式的隱形水印:
- LSB 空域隱形水印
- 頻域水印
創(chuàng)建隱形水印的話,設(shè)置水印的位置和大小顏色就沒有意義了,水印將被編碼到肉眼不可見的空間,只有使用框架的某些方法才能夠成功地檢測出來。
創(chuàng)建一個隱形的水印也是很簡單的,我們同樣可以通過 WatermarkBuilder
來繪制一個隱形的文字或者是圖形水印:
WatermarkBuilder
.create(this, backgroundBitmap)
.loadWatermarkImage(watermarkBitmap)
.setInvisibleWMListener(true, new BuildFinishListener<Bitmap>() {
@Override
public void onSuccess(Bitmap object) {
if (object != null) {
// do something...
}
}
@Override
public void onFailure(String message) {
// do something...
}
});
在方法 .setInvisibleWMListener()
中第一個參數(shù)是用于選擇水印模式的參數(shù):
- true 使用空域 LSB 方法繪制隱形水印
- false 使用頻域隱形水印
同樣,對于隱形水印的檢測,我們可以初始化一個水印檢測器 WatermarkDetector
:
WatermarkDetector
.create(inputBitmap, true)
.detect(false, new DetectFinishListener() {
@Override
public void onSuccess(DetectionReturnValue returnValue) {
Bitmap watermarkImage = returnValue.getWatermarkBitmap();
String watermarkString = returnValue.getWatermarkString();
}
@Override
public void onFailure(String message) {
// do something...
}
});
在函數(shù)創(chuàng)建的時候,也需要選擇一種檢測模式,如果是 LSB 空域水印的檢測,在第二個布爾參數(shù)的位置傳進 true,反正是頻域隱形水印的話就傳 false。在檢測方法 detect()
的第一個參數(shù)也是類似,如果需要檢測文字類的水印傳入 true,圖片類的水印傳入 false。
LSB 實現(xiàn)隱形水印的原理
講完了框架的使用,我們來說一下關(guān)于 androidWM 實現(xiàn)圖片隱形水印的原理,首先要說 LSB 空域水印。LSB 的全稱是 Least Significant Bits,即通過圖形中最不重要的一些信息位來儲存我們隱藏信息(水印)。LSB 方法是最簡單的嵌入水印的方法,事實上,任何一幅圖片都具備一定的容噪性,這表現(xiàn)在像素數(shù)據(jù)的最低有效位 (Least Significant Bit,LSB) 對人眼的視覺影響很小,秘密信息就隱藏在圖像每一個像素的最低位或次低位,實現(xiàn)其不可見性。
為了實現(xiàn)我們的隱形水印,androidWM 將一幅圖片的所有像素提取出來,其中每個像素都可以分解為 ARGB 四位信息(分別是 alpha, red, green, blue),作為標(biāo)準(zhǔn)的 ARGB 編碼方式,每一位都由一個從 0 到 255 的整形數(shù)字表示,其中作為人眼來說,最后一位色階的細(xì)小變化是根本不會被觀察到的,我們將水印的文字(或是圖片)信息編碼為一串二進制的字符,將每個 ARGB 的值的最后一位清零,用二進制字符替換,達(dá)到了利用最后一位色階來儲存隱藏的信息的作用。
在實際的工程中,我們將水印信息編碼成二進制以后,加上了非二進制的前后綴用于識別程序檢測,同時,為了讓整張圖片都被水印給覆蓋,我們的算法將水印信息循環(huán)地鋪設(shè)在背景圖片中,只要圖片之中含有一個有效的水印信息,就能夠成功地檢測到。
對于 LSB 空域數(shù)字水印來說,其有如下優(yōu)點:
- 支持的水印信息量大
- 對原圖影響小
- 算法簡單
但是其也有不可忽視地巨大缺點:
- 在空間域上繪制數(shù)字水印,水印會隨著圖片變化而破壞
- 穩(wěn)定性差,不能抵抗圖像的裁剪、縮放和 jpg 壓縮