Android 水印相机开发

水印相机是自定义相机的一种,实现方法有很多,我看了很多别人的做的很漂亮,我做的就很普通了,不过总算是实现了拍照加水印的功能。

我这边用到了SurfaceView,有人没用这个也做出来水印相机,个人觉得还是SurfaceView更方便一点(不接受反驳)。

先看看效果:


效果图

原图太大,我做了压缩,所以动图显得模糊。

第一步,我们想一进入就打开相机预览,这个怎么做呢?
相机功能由android.hardware.Camera类实现,但是需要有一个预览载体,这里就用SurfaceView,而且需要辅助类SurfaceHolder,首先,我们的 Activity 要实现SurfaceHolder.Callback接口:

public class WaterCameraActivity extends AppCompatActivity implements SurfaceHolder.Callback

第二步,关联SurfaceHolder

        private SurfaceView mSv;
        private SurfaceHolder mSurfaceHolder;
        mSurfaceHolder = mSv.getHolder();
        mSurfaceHolder.setKeepScreenOn(true);
        mSurfaceHolder.setFormat(PixelFormat.TRANSPARENT);
        mSurfaceHolder.addCallback(this);
        // 为了实现照片预览功能,需要将SurfaceHolder的类型设置为PUSH,这样画图缓存就由Camera类来管理,画图缓存是独立于Surface的
        mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

实现SurfaceHolder.Callback接口有三个方法需要重写:

   @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显示,就会调用surfaceCreated(),不显示就会调用surfaceDestroyed()。因此可以在surfaceCreated()中初始化相机,并展示预览界面;在surfaceDestroyed()中释放相机资源。
第三步,初始化相机

            mCamera = Camera.open(0);//0-后摄像头,1-前摄像头
            Camera.getCameraInfo(0, cameraInfo);
            Camera.Parameters parameters = mCamera.getParameters();
            // 设置图片格式
            parameters.setPictureFormat(ImageFormat.JPEG);
            // 设置照片质量
            parameters.setJpegQuality(100);
            // 首先获取系统设备支持的所有颜色特效,如果设备不支持颜色特性将返回一个null, 如果有符合我们的则设置
            List colorEffects = parameters.getSupportedColorEffects();
            Iterator colorItor = colorEffects.iterator();
            while (colorItor.hasNext()) {
                String currColor = colorItor.next();
                if (currColor.equals(Camera.Parameters.EFFECT_SOLARIZE)) {
                    parameters.setColorEffect(Camera.Parameters.EFFECT_AQUA);
                    break;
                }
            }
            // 获取对焦模式
            List focusModes = parameters.getSupportedFocusModes();
            if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
                // 设置自动对焦
                parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
            }

            // 设置闪光灯自动开启
            List flashModes = parameters.getSupportedFlashModes();
            if (flashModes.contains(Camera.Parameters.FLASH_MODE_AUTO)) {
                // 自动闪光
                parameters.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);
            }
            mCamera.setDisplayOrientation(setCameraDisplayOrientation());
            // 设置显示
            mCamera.setPreviewDisplay(mSurfaceHolder);

            List photoSizes = parameters.getSupportedPictureSizes();//获取系统可支持的图片尺寸
            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);
            // 设置完成需要再次调用setParameter方法才能生效
            mCamera.setParameters(parameters);
            // 开始预览
            mCamera.startPreview();

这样就可以预览相机界面了,多说一点,我是在小米 8 手机调试的,照片很清晰,拍出来的照片有 8M 多大,但是换成荣耀 8,图片只有几十 Kb,很不清楚。单步调试的时候可以发现,parameters.getSupportedPictureSizes()这里获取的集合,小米和荣耀排序方式是不一样的,一个是清晰度由低到高,另一个由高到低。所以才改成上面代码中都取最大值:

            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 将会返回图片的字节数组
                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();
                }
            }
        });

手动调用相机拍出来的照片是旋转了 270 度的,所以要再旋转 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);
        //向位图中开始画入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);//过滤一些
        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;

说明几点:

    1. 一开始设置字体大小是 22sp,但是没有显示水印,后来近距离仔细看有水印,只是字体太小,用了 sp 转 px,还是很小,最后发现图片的宽比手机屏宽要大得多,考虑这个倍数,计算出来,字体就可以正常显示了:times = mBitmapWidthF / screenWidth
    1. 字体居中显示:paint.measureText(text)可以计算水印的宽度,屏宽一半减水印宽的一半,就是水印最左端的 x 坐标:
      示意图

      高度我这边是从屏高 3/4 处开始绘制,所以最终就是居中显示在屏幕中下方:
        float x = (mBitmapWidth / 2) - (textW / 2);
        canvas.drawText(text, x, (mBitmapHeight * 3 / 4), paint);
    1. 显示两行水印,并且都居中:下面水印的 y 坐标 = 上面水印 y 坐标 + 上面水印的高度,上面水印高度计算:float textH = -paint.ascent() + paint.descent()
  • 4.图片拍出来很大,压缩一下:
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        image.compress(Bitmap.CompressFormat.JPEG, 100, baos);// 质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
        int options = 98;
        while (baos.toByteArray().length / 1024 > 3072) { // 循环判断如果压缩后图片是否大于 3Mb,大于继续压缩
            baos.reset(); // 重置baos即清空baos
            image.compress(Bitmap.CompressFormat.JPEG, options, baos);// 这里压缩options%,把压缩后的数据存放到baos中
            options -= 2;// 每次都减少2
        }
        ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());// 把压缩后的数据baos存放到ByteArrayInputStream中
        Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);// 把ByteArrayInputStream数据生成图片
        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();
            // 最后通知图库更新
            WaterCameraActivity.this.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + fileName)));
            Toast.makeText(this, "文件已保存至:" + fileName, Toast.LENGTH_LONG).show();

效果图:


效果图

清晰度可以的。


附上源码:点击获取
谢谢!

你可能感兴趣的:(Android 水印相机开发)