[Android] SurfaceView 이용하여 카메라 촬영

2023. 8. 8. 14:37Android

728x90

Android SurfaceView 개발 기록

 

 

1. 일단 SurfaceView class 생성

 

public class CameraSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
    SurfaceHolder holder;
    Camera camera = null;
    private int CAMERA_FACING = Camera.CameraInfo.CAMERA_FACING_FRONT;
    public CameraSurfaceView(Context context) {
        super(context);
        init(context);
    }

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

    private void init(Context context){
        //초기화를 위한 메소드
        holder = getHolder();
        holder.addCallback(this);
    }

    @Override
    public void surfaceCreated(SurfaceHolder surfaceHolder) {
        //만들어지는시점
        camera  = Camera.open(CAMERA_FACING);//카메라 객체 참조
        try{
            camera.setPreviewDisplay(holder);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
        //변경
        camera.startPreview(); //렌즈로 부터 들어오는 영상을 뿌려줌
        camera.stopPreview();
//        camera.setDisplayOrientation(90);//카메라 미리보기 오른쪽 으로 90 도회전
        camera.startPreview();

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
        //소멸
        camera.stopPreview();//미리보기중지
        camera.release();
        camera = null;
    }

    public boolean capture(Camera.PictureCallback callback){
        if(camera != null){
            camera.startPreview();
            camera.enableShutterSound(false);
            camera.takePicture(null,null,callback);
            return true;
        }
        else{
            return false;
        }
    }
}

 

 

2. xml 파일 - 웹뷰로 앱을 만들었는데 메인화면 WebView 위에 CardView 올리고 그 안에 내가만든 SurfaceView를 넣었다.

** 화면 왼쪽 하단부분에 동그랗게 카메라 미리보기를 보이게하기위해 CardView안에 SurfaceView를 넣었다.

 

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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=".activity.MainActivity">

    <WebView
        android:id="@+id/webview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
    

    <androidx.cardview.widget.CardView
        android:id="@+id/cardView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:layout_margin="20dp"
        app:cardCornerRadius="100dp">

        <kr.co.nton.skycode.webview.CameraSurfaceView
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:id="@+id/surfaceView" />
    </androidx.cardview.widget.CardView>
    
</FrameLayout>

 

 

3. MainActivity에서 타이머 돌리기

 

public TimerHandler getTimerHandler() {
    return timerHandler;
}

public class TimerHandler extends Handler {

    public void startTimer() {

        this.removeMessages(MESSAGE_TIMER_START);

        Log.d("aa", "시작");
        capture();
        this.sendEmptyMessageDelayed(MESSAGE_TIMER_START, TIMER_DELAY);
    }

    @Override
    public void handleMessage(@NonNull Message msg) {
        super.handleMessage(msg);
        switch (msg.what) {
            case MESSAGE_TIMER_START:
                // 타이머를 다시 시작
                startTimer();
                break;
        }
    }
}

 

 

4. 사진 촬영 후 이미지를 저장하거나 뷰로 보여줄 수 있다.

카메라 방향조정, 이미지 압축 가능

 

    public void capture() {
        surfaceView.capture(new Camera.PictureCallback() {
            @Override
            public void onPictureTaken(byte[] data, Camera camera) {

                //카메라 방향 조정
                int rotation = MainActivity.this.getWindowManager().getDefaultDisplay().getRotation();

                int degrees = 0;

                switch (rotation) {

                    case Surface.ROTATION_0:
                        degrees = 0;
                        break;

                    case Surface.ROTATION_90:
                        degrees = 90;
                        break;

                    case Surface.ROTATION_180:
                        degrees = 180;
                        break;

                    case Surface.ROTATION_270:
                        degrees = 270;
                        break;

                }

                int result = (90 - degrees + 360) % 360;

                camera.setDisplayOrientation(result);

                //bytearray 형식으로 전달
                //이걸이용해서 이미지뷰로 보여주거나 파일로 저장
                BitmapFactory.Options options = new BitmapFactory.Options();
                options.inSampleSize = 8; // 1/8사이즈로 보여주기
                Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); //data 어레이 안에 있는 데이터 불러와서 비트맵에 저장

                int width = bitmap.getWidth();
                int height = bitmap.getHeight();
                int newWidth = 600;
                int newHeight = 480;


                float scaleWidth = ((float) newWidth) / width;
                float scaleHeight = ((float) newHeight) / height;

                Matrix matrix = new Matrix();

//                matrix.postScale(scaleWidth, scaleHeight);

                //전면카메라 좌우반전
                matrix.setScale(-1, 1);
                matrix.postTranslate(newWidth, 0);
                matrix.postRotate(result);

                //1차로 좌우반전 회전 적용
                Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
                //2차로 크기 600x480 적용
                Bitmap scaledBitmap = Bitmap.createScaledBitmap(resizedBitmap, newWidth, newHeight, true);

                BitmapToString(scaledBitmap);
                Log.d("base64", String.valueOf(scaledBitmap));

//                imageView.setImageDrawable(new BitmapDrawable(scaledBitmap));//이미지뷰에 사진 보여주기


//                @SuppressLint("SimpleDateFormat")
//                String timeStamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
//                String imageFileName = timeStamp;
//
//                //사진저장코드
//                String imageSaveUri = MediaStore.Images.Media.insertImage(getContentResolver(), scaledBitmap, imageFileName, "저장되었습니다.");
//                Uri uri = Uri.parse(imageSaveUri);
//                sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_STARTED, uri));
//                Toast.makeText(getApplicationContext(), "찍은 사진이 저장되었습니다.", Toast.LENGTH_SHORT).show();
                //
                camera.startPreview();
            }
        });
    }
//이미지 압축 및 base64로 변환
public void BitmapToString(Bitmap bitmap) {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    bitmap.compress(Bitmap.CompressFormat.JPEG, 50, baos);
    byte[] bytes = baos.toByteArray();
    String temp = Base64.encodeToString(bytes, Base64.NO_WRAP);
    Log.d("base64", temp);
}

 

 

5. 이제 원하는곳에 타이머 시작, 멈춤을 하면된다. WebViewClient class onPageFinished 에서 url이 변할때 url를 보고 타이머를 실행하는 구조이다.

 

MainActivity 타이머를 사용하기위해 timerHandler = (MainActivity) view.getcontext()).getTimerHadler();

xml에서 CardView안에 넣었던 SurfaceView 카메라 미리보기를 숨겼다 보였다 하기위해 MainActivity mainActivity = (MainActivity) view.getContext(); 

 

private MainActivity.TimerHandler timerHandler;
@Override
public void onPageFinished(WebView view, String url) {
    super.onPageFinished(view, url);

    //MainActivity 함수 사용하기 위해
    MainActivity mainActivity = (MainActivity) view.getContext();
    timerHandler = ((MainActivity) view.getContext()).getTimerHandler();
    if (url.equals("https://test.co.kr")) {
        mainActivity.setSurfaceViewVisibility(View.VISIBLE);
        timerHandler.sendEmptyMessageDelayed(MESSAGE_TIMER_START, TIMER_DELAY);

    } else {
        mainActivity.setSurfaceViewVisibility(View.GONE);
        timerHandler.removeMessages(MESSAGE_TIMER_START);
    }
    
}

 

 

** MainActivity Activity

activity_main.xml에 SurfaceView 를 다른 클래스에서도 사용할 수 있게 함수를 만들어서 사용했다.

 

CameraSurfaceView surfaceView;
public void setSurfaceViewVisibility(int visibility) {
    surfaceView.setVisibility(visibility);
}

 

 

 

728x90