欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

自定义Camera实现头像框效果,并裁剪指定区域合成

程序员文章站 2022-03-11 11:06:25
...

需要一个带框的相机,并且拍好后能合成框和人脸,不过需要人自己凑过去哈哈哈

这两天看了很多博客,然后自己根据自己的要求改了改,基本可以用,调节参数可以获得想要的效果

参考链接在最后面,要是看不懂我的,可以去看看他们的,都挺有参考价值的

效果图:

自定义Camera实现头像框效果,并裁剪指定区域合成自定义Camera实现头像框效果,并裁剪指定区域合成    

前一张为拍照界面,绿色的是拍照按钮。。。第二章为保存的照片,叠加了背景,中间是裁剪出来的,最上面一层是图片,一共三层。

实现思路:

自定义相机

在surface界面上加一个自定义的view,画上(或者直接覆盖)这张图片,

拍好后,在照片上裁剪出小恐龙图片大小的区域

选好背景,调好裁剪后的照片和小恐龙的位置,叠加照片

注意点:

裁剪时,比例是按照预览窗口中小恐龙控件的比例去裁剪

由于是自定义的相机,所以预览窗口(Preview)大小和照片(Picture)的大小都需要自己设置过。我这里是给了他两个相同的固定值,使其表现的一样。

需要适配的话,可以修改代码中的changePreviewSize方法,在其中设置预览窗口和照片大小,注意,一定要设置成宽高比例差不多的,不然保存的照片会出现拉伸的问题。

相机的自动聚焦,对于不同的机型可能会出现问题,问题的解决参考链接在最后

对于6.0以后,权限的申请我用的是第三方,EasyPermissions,使用方法见参考链接

不BB了,代码

1.在gradle里添加依赖(EasyPermissions)
    implementation 'pub.devrel:easypermissions:1.3.0'

2.加权限

 <uses-permission android:name="android.permission.CAMERA"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

3.activity_main.xml


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <FrameLayout
        android:id="@+id/myFramelayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@drawable/combine_pic3" />

        <SurfaceView
            android:id="@+id/surfaceView"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

        <com.example.zhang.diycamerademo01.MyView
            android:id="@+id/myView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center" />

    </FrameLayout>

    <LinearLayout
        android:id="@+id/linear"
        android:layout_width="55dp"
        android:layout_height="match_parent"
        android:layout_alignParentRight="true"
        android:layout_marginRight="8dp"
        android:gravity="center_vertical">

        <Button
            android:id="@+id/takepicture_button"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:background="@drawable/ic_launcher_background"
            android:onClick="takepicture" />

    </LinearLayout>

</RelativeLayout>

4.MyView.java


public class MyView extends View {
    //获取屏幕的宽和高。根据屏幕的宽和高来算框的位置
    private int screenWidth, screenHeight;

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public void setMyParams(int screenWidth, int screenHeight) {
        this.screenWidth = screenWidth;
        this.screenHeight = screenHeight;

    }

    @Override
    public void onDraw(Canvas canvas) {
        Bitmap bg1 = ((BitmapDrawable) getResources().getDrawable(R.drawable.combine_touxiang3)).getBitmap();
       //Bitmap toghter = ((BitmapDrawable) getResources().getDrawable(R.drawable.combine_toghter1small)).getBitmap();
        //画一个头像框,正中间
        canvas.drawBitmap(bg1,(screenWidth-bg1.getWidth())/2,(screenHeight-bg1.getHeight())/2,null);
        //canvas.drawBitmap(toghter,0,0,null);
        super.onDraw(canvas);
    }
}

public class MainActivity extends AppCompatActivity implements EasyPermissions.PermissionCallbacks {
    private static final String TAG = "TEST";
    private MyView myView;
    private Camera camera;
    private SurfaceView surfaceview;
    private int screenWidth, screenHeight;
        private ToneGenerator tone;
    private Camera.Parameters parameters;
    //剪切后的图片宽高
    private int retX, retY;
    private float radioX,radioY;
    String[] permissions = new String[]{
            Manifest.permission.CAMERA,
            Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.READ_EXTERNAL_STORAGE
    };
    // 声明一个集合,在后面的代码中用来存储用户拒绝授权的权
    List<String> mPermissionList = new ArrayList<>();
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        //把屏幕设置成横屏
        setContentView(R.layout.activity_main);
        //全屏
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        initRequest();

        WindowManager wm = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE);
        screenWidth = wm.getDefaultDisplay().getWidth();
        screenHeight = wm.getDefaultDisplay().getHeight();

        initScale();

        myView =  findViewById(R.id.myView);
        myView.setMyParams(screenWidth, screenHeight);
        FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) myView.getLayoutParams();
        layoutParams.width = screenWidth;
        layoutParams.height = screenHeight;
        myView.setLayoutParams(layoutParams);

        initDIYCamera();

    }
      private Camera.AutoFocusCallback myAutoFocusCallback = new Camera.AutoFocusCallback() {
        @Override
        public void onAutoFocus(boolean success, Camera camera) {
            Log.w("print", "聚焦完成,,,,");
            //聚焦后发出提示音
            tone = new ToneGenerator(AudioManager.STREAM_MUSIC, ToneGenerator.MAX_VOLUME);
            tone.startTone(ToneGenerator.TONE_PROP_BEEP2);
        }
    };

    private void initScale(){
         Bitmap bg3 = ((BitmapDrawable) getResources().getDrawable(R.drawable.combine_touxiang3)).getBitmap();
        int test1=bg3.getHeight();
        int test2=bg3.getWidth();
        radioX=(float) test2/screenWidth;
        radioY=(float) test1/screenHeight;
    }

    private void initDIYCamera() {
        //当第一次进来时,首先执行这个方法,因为没有授权所以找不到相机
        //所以需要再授权后再次找到相机
        myView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                camera.autoFocus(myAutoFocusCallback);
                return false;
            }
        });
        camera = Camera.open(0);
        surfaceview = (SurfaceView) findViewById(R.id.surfaceView);
        SurfaceHolder holder = surfaceview.getHolder();
        holder.addCallback(new MySurfaceCallback());
        holder.setKeepScreenOn(true);// 屏幕常亮 holder.setType(SurfaceHolder.SURFACE_TYPE_NORMAL);
        holder.lockCanvas();
    }

    private void initRequest() {
        //使用easyPermissions
        //先检查是否已经授权
        if (EasyPermissions.hasPermissions(this, permissions)) {

            // 已经申请过权限,做想做的事
        } else {
            // 没有申请过权限,现在去申请,第二个参数为拒绝后再次申请时弹出的文字
            EasyPermissions.requestPermissions(this, "快来申请我哦",
                    1, permissions);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        // 把执行结果的操作给EasyPermissions
        EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
    }

    //申请成功时调用
    @Override
    public void onPermissionsGranted(int requestCode, @NonNull List<String> perms) {
//请求成功执行相应的操作

        //比如,举个例子
        switch (requestCode) {
            case 1:
                //第一次进来,初始化时camera为空
               camera.open(0);
                Toast.makeText(this, "成功获取3个权限", Toast.LENGTH_SHORT).show();
                break;

        }
    }

    @Override
    public void onPermissionsDenied(int requestCode, @NonNull List<String> perms) {
        //比如,举个例子
        switch (requestCode) {
            case 1:
                Toast.makeText(this, "3个权限未被全部同意", Toast.LENGTH_SHORT).show();
                break;

        }
    }


    private final class MySurfaceCallback implements SurfaceHolder.Callback {
        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            //当surface的格式或大小发生改变,这个方法就被调用

        }

        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            try {
                if (camera==null){
                    camera = Camera.open(0);
                }
                camera.stopPreview();
                camera.release();
                camera = null;
                camera = Camera.open(0);

                //设置相机预览时大小及图片的大小和camera的参数设置
                changePreviewSize(camera, surfaceview.getWidth(), surfaceview.getHeight());

                camera.setPreviewDisplay(surfaceview.getHolder());
                // 开启预览
                camera.startPreview();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            if (camera != null) {
                camera.release();
                camera = null;
            }
        }
    }

    public void takepicture(View v) {
        camera.takePicture(mShutterCallback, null, mPictureCallback);
    }

    Camera.ShutterCallback mShutterCallback = new Camera.ShutterCallback() {
        public void onShutter() {
        }
    };

    private Camera.PictureCallback mPictureCallback = new Camera.PictureCallback() {
        @Override
        public void onPictureTaken(byte[] data, Camera camera) {
            Bitmap mBitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
            Bitmap mBitmapCut = ImageCrop(mBitmap);

            Bitmap bg1 = ((BitmapDrawable) getResources().getDrawable(R.drawable.combine_pic1)).getBitmap();
            Bitmap bg3 = ((BitmapDrawable) getResources().getDrawable(R.drawable.combine_touxiang3)).getBitmap();

            Bitmap mBitmapFinal = CombineImage(bg1, mBitmapCut, bg3);

            String rootPath = Environment.getExternalStorageDirectory().toString() + File.separator;
            File file = new File(rootPath + System.currentTimeMillis() + ".jpg");
            try {
                file.createNewFile();
                BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(file));
                mBitmapFinal.compress(Bitmap.CompressFormat.JPEG, 100, os);//100 是压缩率,100表示不压缩 os.flush();
                os.close();
                Toast.makeText(getApplicationContext(), "保存成功", Toast.LENGTH_SHORT).show();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    };


    /**
     * 修改相机的预览尺寸和保存的相片的尺寸以及camera的属性
     *
     * @param camera     相机实例
     * @param viewWidth  预览的surfaceView的宽
     * @param viewHeight 预览的surfaceView的高
     */
    protected void changePreviewSize(Camera camera, int viewWidth, int viewHeight) {
        if (camera == null) {
            return;
        }
        parameters = camera.getParameters();
        List<Camera.Size> sizeList = parameters.getSupportedPreviewSizes();
        Camera.Size closelySize = null;//储存最合适的尺寸
        for (Camera.Size size : sizeList) { //先查找preview中是否存在与surfaceview相同宽高的尺寸
            Log.i(TAG, "preview size.width=" + size.width + "  size.height=" + size.height);

            if ((size.width == viewWidth) && (size.height == viewHeight)) {
                closelySize = size;
            }
        }
        if (closelySize == null) {
            // 得到与传入的宽高比最接近的size
            float reqRatio = ((float) viewWidth) / viewHeight;
            float curRatio, deltaRatio;
            float deltaRatioMin = Float.MAX_VALUE;
            for (Camera.Size size : sizeList) {
                if (size.width < 1024) continue;//1024表示可接受的最小尺寸,否则图像会很模糊,可以随意修改
                curRatio = ((float) size.width) / size.height;
                deltaRatio = Math.abs(reqRatio - curRatio);
                if (deltaRatio < deltaRatioMin) {
                    deltaRatioMin = deltaRatio;
                    closelySize = size;
                }
            }
        }

        //设置裁剪后全屏高清的代码
        // 获取摄像头支持的PictureSize列表
        List<Camera.Size> pictureSizeList = parameters.getSupportedPictureSizes();
        List<Camera.Size> middlePicSizeList = new ArrayList<>();//储存比例最接近且与预览窗口最接近大小的尺寸
        Camera.Size closelyPicSize = null;//储存最合适的照片尺寸
        for (Camera.Size size : pictureSizeList) { //先查找Pic中是否存在与surfaceview相同宽高的尺寸
            Log.i(TAG, "pictureSizeList size.width=" + size.width + "  size.height=" + size.height);
            if ((size.width == viewWidth) && (size.height == viewHeight)) {
                closelyPicSize = size;
            }
        }
        if (closelyPicSize == null) {
            // 得到与传入的宽高比最接近的PicSize
            float reqRatio = ((float) viewWidth) / viewHeight;
            float curRatio, deltaRatio;
            float deltaRatioMin = Float.MAX_VALUE;
            for (Camera.Size size : pictureSizeList) {
                if (size.width < 1024) continue;//1024表示可接受的最小尺寸,否则图像会很模糊,可以随意修改
                curRatio = ((float) size.width) / size.height;
                deltaRatio = Math.abs(reqRatio - curRatio);
                if (deltaRatio < deltaRatioMin) {
                    deltaRatioMin = deltaRatio;
                    middlePicSizeList.add(size);
                }
            }
            for (Camera.Size size:middlePicSizeList){
                float middleSize;
                Log.i(TAG, "pictureMiddleList size.width=" + size.width + "  size.height=" + size.height);

                float middleSi* = Float.MAX_VALUE;
                middleSize = Math.abs(size.width - closelySize.width);
                if (middleSize<middleSi*){
                    middleSi* = middleSize;
                    closelyPicSize=size;
                }
            }
        }

        if (closelySize != null) {
            Log.i("changePreviewSize", "预览尺寸修改为:" + closelySize.width + "*" + closelySize.height);
            //设置大小
            //parameters.setPreviewSize(closelySize.width, closelySize.height);
            //由于裁剪是依据preview界面中MyView的比例来算的,所以preview界面和保存的照片的
            // 宽高比例要一致,才不会导致拉伸
            parameters.setPreviewSize(1920, 1080);
            //设置图片的大小,同样可以和preview一样修改,只是现在把他弄成了固定数值
            parameters.setPictureSize(1920, 1080);
            //设置自动对焦
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
            camera.setParameters(parameters);
        }
    }

    /**
     * 按图片比例裁切图片
     */
    public Bitmap ImageCrop(Bitmap bitmap) {
        // 得到图片的宽,高
        int w = bitmap.getWidth();
        int h = bitmap.getHeight();

        //剪切的初始xy位置,原点位置都是根据preview
        //界面的控件比例来算的
        int x = (int) ((w-(w * radioX ))/2);
        int y = (int) ((h-(h * radioY )) /2);
        //剪切后的宽和高
        retX = (int) (w * radioX);
        retY = (int) (h * radioY);

        Bitmap afterCrop = Bitmap.createBitmap(bitmap, x, y, retX, retY, null, false);
        return afterCrop;
    }

    /*
    叠加图片
     */
    public Bitmap CombineImage(Bitmap b1, Bitmap b2, Bitmap b3) {
        //创建一张大小和背景图一致的位图
        int bgWidth = b1.getWidth();
        int bgHeight = b1.getHeight();
        int fgWidth = b2.getWidth(); //前景图宽度,用于后面计算前景图的绘制坐标
        int fgHeight = b2.getHeight(); //前景图高度,用于后面计算前景图的绘制坐标
        Bitmap newbmp = Bitmap.createBitmap(bgWidth, bgHeight, Bitmap.Config.RGB_565);

        //缩放照片
        int beforeX=b3.getWidth();
        int beforeY=b3.getHeight();
        float scaleWidth = ((float) retX) / beforeX;
        float scaleHeight = ((float) retY) / beforeY;
        // 取得想要缩放的matrix參數
        Matrix matrix = new Matrix();
        matrix.postScale(scaleWidth, scaleHeight);//两个参数未缩放的比例
        Bitmap afterCrop = Bitmap.createBitmap(b3, 0, 0, beforeX, beforeY, matrix, true);
        
//新建画布
        Canvas cv = new Canvas(newbmp);
//在0,0坐标开始画入bg
        cv.drawBitmap(b1, 0, 0, null);
//开始画入fg,可以从任意位置画入,具体位置自己计算f
        cv.drawBitmap(b2, (bgWidth - fgWidth) / 2, (bgHeight - fgHeight) / 2, null);
//开始画入第三层,可以从任意位置画入,具体位置自己计算f
        cv.drawBitmap(afterCrop, (bgWidth - fgWidth) / 2, (bgHeight - fgHeight) / 2, null);
        return newbmp;
    }
}

 

总结:

这个demo还是很不完整的,很多的细节,比如位置等,都是需要自己看着调的,自己通过改一些东西也能实现大部分功能。

加油!!!哈哈哈

 

demo下载:https://download.csdn.net/download/jiye111/10626903

 

参考链接:

自定义相机: https://blog.csdn.net/ming_csdn_/article/details/70154381

自动对焦:https://blog.csdn.net/yanzi1225627/article/details/8577682

图片叠加功能:https://www.jb51.net/article/138971.htm

EasyPermissions使用方法:https://blog.csdn.net/u011138190/article/details/79776902

Fail to connect to camera service的几种原因和解决方法:https://blog.csdn.net/wang_shuai_ww/article/details/44560113

 

相关标签: 自定义控件