水印相機(jī)是自定義相機(jī)的一種,實(shí)現(xiàn)方法有很多,我看了很多別人的做的很漂亮,我做的就很普通了,不過(guò)總算是實(shí)現(xiàn)了拍照加水印的功能。
我這邊用到了SurfaceView,有人沒(méi)用這個(gè)也做出來(lái)水印相機(jī),個(gè)人覺(jué)得還是SurfaceView更方便一點(diǎn)(不接受反駁)。
先看看效果:

原圖太大,我做了壓縮,所以動(dòng)圖顯得模糊。
第一步,我們想一進(jìn)入就打開(kāi)相機(jī)預(yù)覽,這個(gè)怎么做呢?
相機(jī)功能由android.hardware.Camera類實(shí)現(xiàn),但是需要有一個(gè)預(yù)覽載體,這里就用SurfaceView,而且需要輔助類SurfaceHolder,首先,我們的 Activity 要實(shí)現(xiàn)SurfaceHolder.Callback接口:
public class WaterCameraActivity extends AppCompatActivity implements SurfaceHolder.Callback
第二步,關(guān)聯(lián)SurfaceHolder:
private SurfaceView mSv;
private SurfaceHolder mSurfaceHolder;
mSurfaceHolder = mSv.getHolder();
mSurfaceHolder.setKeepScreenOn(true);
mSurfaceHolder.setFormat(PixelFormat.TRANSPARENT);
mSurfaceHolder.addCallback(this);
// 為了實(shí)現(xiàn)照片預(yù)覽功能,需要將SurfaceHolder的類型設(shè)置為PUSH,這樣畫圖緩存就由Camera類來(lái)管理,畫圖緩存是獨(dú)立于Surface的
mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
實(shí)現(xiàn)SurfaceHolder.Callback接口有三個(gè)方法需要重寫:
@Override
public void surfaceCreated(SurfaceHolder holder) {
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
只要SurfaceView顯示,就會(huì)調(diào)用surfaceCreated(),不顯示就會(huì)調(diào)用surfaceDestroyed()。因此可以在surfaceCreated()中初始化相機(jī),并展示預(yù)覽界面;在surfaceDestroyed()中釋放相機(jī)資源。
第三步,初始化相機(jī)
mCamera = Camera.open(0);//0-后攝像頭,1-前攝像頭
Camera.getCameraInfo(0, cameraInfo);
Camera.Parameters parameters = mCamera.getParameters();
// 設(shè)置圖片格式
parameters.setPictureFormat(ImageFormat.JPEG);
// 設(shè)置照片質(zhì)量
parameters.setJpegQuality(100);
// 首先獲取系統(tǒng)設(shè)備支持的所有顏色特效,如果設(shè)備不支持顏色特性將返回一個(gè)null, 如果有符合我們的則設(shè)置
List<String> colorEffects = parameters.getSupportedColorEffects();
Iterator<String> colorItor = colorEffects.iterator();
while (colorItor.hasNext()) {
String currColor = colorItor.next();
if (currColor.equals(Camera.Parameters.EFFECT_SOLARIZE)) {
parameters.setColorEffect(Camera.Parameters.EFFECT_AQUA);
break;
}
}
// 獲取對(duì)焦模式
List<String> focusModes = parameters.getSupportedFocusModes();
if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
// 設(shè)置自動(dòng)對(duì)焦
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
}
// 設(shè)置閃光燈自動(dòng)開(kāi)啟
List<String> flashModes = parameters.getSupportedFlashModes();
if (flashModes.contains(Camera.Parameters.FLASH_MODE_AUTO)) {
// 自動(dòng)閃光
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);
}
mCamera.setDisplayOrientation(setCameraDisplayOrientation());
// 設(shè)置顯示
mCamera.setPreviewDisplay(mSurfaceHolder);
List<Camera.Size> photoSizes = parameters.getSupportedPictureSizes();//獲取系統(tǒng)可支持的圖片尺寸
int width = 0, height = 0;
for (Camera.Size size : photoSizes) {
if (size.width > width) width = size.width;
if (size.height > height) height = size.height;
}
parameters.setPictureSize(width, height);
// 設(shè)置完成需要再次調(diào)用setParameter方法才能生效
mCamera.setParameters(parameters);
// 開(kāi)始預(yù)覽
mCamera.startPreview();
這樣就可以預(yù)覽相機(jī)界面了,多說(shuō)一點(diǎn),我是在小米 8 手機(jī)調(diào)試的,照片很清晰,拍出來(lái)的照片有 8M 多大,但是換成榮耀 8,圖片只有幾十 Kb,很不清楚。單步調(diào)試的時(shí)候可以發(fā)現(xiàn),parameters.getSupportedPictureSizes()這里獲取的集合,小米和榮耀排序方式是不一樣的,一個(gè)是清晰度由低到高,另一個(gè)由高到低。所以才改成上面代碼中都取最大值:
int width = 0, height = 0;
for (Camera.Size size : photoSizes) {
if (size.width > width) width = size.width;
if (size.height > height) height = size.height;
}
第四步,拍照
mCamera.takePicture(null, null, new android.hardware.Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, android.hardware.Camera camera) {//data 將會(huì)返回圖片的字節(jié)數(shù)組
bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
if (bitmap != null) {
Matrix m = new Matrix();
m.postRotate(90);
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), m, true);
bitmap = compressImage(bitmap);
loadingTv.setVisibility(View.GONE);
cameraBtn.setVisibility(View.INVISIBLE);
cancelBtn.setVisibility(View.VISIBLE);
sureBtn.setVisibility(View.VISIBLE);
wordTv.setVisibility(View.INVISIBLE);
dateTv.setVisibility(View.INVISIBLE);
bitmap = addWater(bitmap);
pictureLinear.setVisibility(View.VISIBLE);
mSv.setVisibility(View.INVISIBLE);
pictureIv.setImageBitmap(bitmap);
} else {
releaseCamera();
}
}
});
手動(dòng)調(diào)用相機(jī)拍出來(lái)的照片是旋轉(zhuǎn)了 270 度的,所以要再旋轉(zhuǎn) 90 度,才是正常視角m.postRotate(90)。
第五步,加水印操作 addWater(bitmap):
android.graphics.Bitmap.Config bitmapConfig =
mBitmap.getConfig();
if (bitmapConfig == null) {
bitmapConfig = android.graphics.Bitmap.Config.ARGB_8888;
}
//獲取原始圖片與水印圖片的寬與高
int mBitmapWidth = mBitmap.getWidth();
int mBitmapHeight = mBitmap.getHeight();
DisplayMetrics dm = getResources().getDisplayMetrics();
float screenWidth = dm.widthPixels;//1080
float mBitmapWidthF = mBitmapWidth;
times = mBitmapWidthF / screenWidth;
Bitmap mNewBitmap = Bitmap.createBitmap(mBitmapWidth, mBitmapHeight, bitmapConfig);
Canvas canvas = new Canvas(mNewBitmap);
//向位圖中開(kāi)始畫入MBitmap原始圖片
canvas.drawBitmap(mBitmap, 0, 0, null);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.WHITE);
paint.setDither(true); //獲取跟清晰的圖像采樣
paint.setFilterBitmap(true);//過(guò)濾一些
paint.setTextSize(sp2px(this, 22) * times);
String text = "裝逼水印";
Rect bounds = new Rect();
paint.getTextBounds(text, 0, text.length(), bounds);
float textW = paint.measureText(text);
float x = (mBitmapWidth / 2) - (textW / 2);
float textH = -paint.ascent() + paint.descent();
canvas.drawText(text, x, (mBitmapHeight * 3 / 4), paint);//mBitmapWidth=3024
paint.setTextSize(sp2px(this, 20) * times);
paint.getTextBounds(date, 0, date.length(), bounds);
textW = paint.measureText(date);
x = (mBitmapWidth / 2) - (textW / 2);
canvas.drawText(date, x, (mBitmapHeight * 3 / 4) + textH, paint);
canvas.save(Canvas.ALL_SAVE_FLAG);
return mNewBitmap;
說(shuō)明幾點(diǎn):
- 一開(kāi)始設(shè)置字體大小是 22sp,但是沒(méi)有顯示水印,后來(lái)近距離仔細(xì)看有水印,只是字體太小,用了 sp 轉(zhuǎn) px,還是很小,最后發(fā)現(xiàn)圖片的寬比手機(jī)屏寬要大得多,考慮這個(gè)倍數(shù),計(jì)算出來(lái),字體就可以正常顯示了:
times = mBitmapWidthF / screenWidth
- 一開(kāi)始設(shè)置字體大小是 22sp,但是沒(méi)有顯示水印,后來(lái)近距離仔細(xì)看有水印,只是字體太小,用了 sp 轉(zhuǎn) px,還是很小,最后發(fā)現(xiàn)圖片的寬比手機(jī)屏寬要大得多,考慮這個(gè)倍數(shù),計(jì)算出來(lái),字體就可以正常顯示了:
- 字體居中顯示:
paint.measureText(text)可以計(jì)算水印的寬度,屏寬一半減水印寬的一半,就是水印最左端的 x 坐標(biāo):
示意圖
高度我這邊是從屏高 3/4 處開(kāi)始繪制,所以最終就是居中顯示在屏幕中下方:
- 字體居中顯示:
float x = (mBitmapWidth / 2) - (textW / 2);
canvas.drawText(text, x, (mBitmapHeight * 3 / 4), paint);
- 顯示兩行水印,并且都居中:
下面水印的 y 坐標(biāo) = 上面水印 y 坐標(biāo) + 上面水印的高度,上面水印高度計(jì)算:float textH = -paint.ascent() + paint.descent()
- 顯示兩行水印,并且都居中:
- 4.圖片拍出來(lái)很大,壓縮一下:
ByteArrayOutputStream baos = new ByteArrayOutputStream();
image.compress(Bitmap.CompressFormat.JPEG, 100, baos);// 質(zhì)量壓縮方法,這里100表示不壓縮,把壓縮后的數(shù)據(jù)存放到baos中
int options = 98;
while (baos.toByteArray().length / 1024 > 3072) { // 循環(huán)判斷如果壓縮后圖片是否大于 3Mb,大于繼續(xù)壓縮
baos.reset(); // 重置baos即清空baos
image.compress(Bitmap.CompressFormat.JPEG, options, baos);// 這里壓縮options%,把壓縮后的數(shù)據(jù)存放到baos中
options -= 2;// 每次都減少2
}
ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());// 把壓縮后的數(shù)據(jù)baos存放到ByteArrayInputStream中
Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);// 把ByteArrayInputStream數(shù)據(jù)生成圖片
return bitmap;
第六步,保存水印圖:
FileOutputStream outStream = null;
String filePath = Environment.getExternalStorageDirectory().getPath() + File.separator + "testPhoto";
String fileName = filePath + File.separator + String.valueOf(System.currentTimeMillis()) + ".jpg";
File file = new File(fileName);
if (!file.exists()) file.getParentFile().mkdirs();
outStream = new FileOutputStream(fileName);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outStream);
if (outStream != null) outStream.close();
// 最后通知圖庫(kù)更新
WaterCameraActivity.this.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + fileName)));
Toast.makeText(this, "文件已保存至:" + fileName, Toast.LENGTH_LONG).show();
效果圖:

清晰度可以的。
附上源碼:點(diǎn)擊獲取
謝謝!
