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<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;
                }
            }
            // 获取对焦模式
            List<String> focusModes = parameters.getSupportedFocusModes();
            if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
                // 设置自动对焦
                parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
            }

            // 设置闪光灯自动开启
            List<String> 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<Camera.Size> 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();

效果图:


效果图

清晰度可以的。


附上源码点击获取
谢谢!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容