Android 水印相機(jī)開(kāi)發(fā)

水印相機(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):

    1. 一開(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
    1. 字體居中顯示: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);
    1. 顯示兩行水印,并且都居中:下面水印的 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)擊獲取
謝謝!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容