정상에서 IT를 외치다

[Android, Camera] 1. 카메라 프리뷰를 이용한 화면 캡처 및 배경 이미지 적용 본문

안드로이드

[Android, Camera] 1. 카메라 프리뷰를 이용한 화면 캡처 및 배경 이미지 적용

Black-Jin 2018. 8. 22. 14:52
반응형

안녕하세요. 블랙진입니다.


Capture the camera preview with image in android


이번에는 Camera api 를 사용하여 화면을 캡처하고 캡처한 화면에 배경 이미지를  적용하는 방법에 대해 포스팅 해보겠습니다.


안드로이드 카메라 API 문서


먼저 위 문서를 보면 아래와 같이 Note 가 있습니다.


 Camera2 Api 사용을 권장하는 문구입니다. 안드로이드 카메라에 관한 다양한 기능을 사용할 수 있다고 소개되어 있는데 저는 아래와 같은 이유로 Camera api 를 사용했습니다.


1. Camera2 Api  는 안드로이드 5.0 (롤리팝) 이상부터 사용 가능합니다. 저는 안드로이드 4.4 (킷캣) 사용자도 사용 할 수 있게 만들었습니다.


2. Camera2 Api 는 카메라의 많은 기능을 사용 할 수 있지만 그만큼 초기 코드 설정이 복잡합니다. 공부양이 방대하여 일단 Camera Api 를 사용했습니다.


그럼 코드 리뷰 포스팅을 시작해 보겠습니다.~!




0. 상단 바 제거


_style.xml

<resources>

<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<!-- 타이틀 바를 안보이도록 합니다. -->
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>

</resources>

카메라를 띄울 화면 상단에 바를 제거하는 코드입니다.




1. 권한 설정


안드로이드 6.0 (마시멜로우) 이상 버전부터는 권한 설정이 필수 입니다. 저희는 카메라 사용과 캡처된 이미지 저장을 위한 2가지 권한이 필요합니다.


_AndroidManifest.xml

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



_MainActivity.kt

class MainActivity : AppCompatActivity() {

private val TAG = "MyTag"

private val PERMISSIONS_REQUEST_CODE = 100

private val REQUIRED_PERMISSIONS =
arrayOf(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE)

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

// 상태바를 안보이도록 합니다.
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN)

// 화면 켜진 상태를 유지합니다.
window.setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)

setContentView(R.layout.activity_main)

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// OS가 Marshmallow 이상일 경우 권한체크를 해야 합니다.

val permissionCheckCamera
= ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
val permissionCheckStorage
= ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)

if (permissionCheckCamera == PackageManager.PERMISSION_GRANTED

&& permissionCheckStorage == PackageManager.PERMISSION_GRANTED) {

// 권한 있음
Log.d(TAG, "권한 이미 있음")
startCamera()


} else {

// 권한 없음
Log.d(TAG, "권한 없음")
ActivityCompat.requestPermissions(this,
REQUIRED_PERMISSIONS,
PERMISSIONS_REQUEST_CODE)


}


} else {
// OS가 Marshmallow 이전일 경우 권한체크를 하지 않는다.
Log.d("MyTag", "마시멜로 버전 이하로 권한 이미 있음")
startCamera()

}
}

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
// grantResults[0] 거부 -> -1
// grantResults[0] 허용 -> 0 (PackageManager.PERMISSION_GRANTED)

Log.d(TAG, "requestCode : $requestCode, grantResults size : ${grantResults.size}")

if(requestCode == PERMISSIONS_REQUEST_CODE) {

var check_result = true

for(result in grantResults) {
if(result != PackageManager.PERMISSION_GRANTED) {
check_result = false
break
}
}

if(check_result) {

startCamera()

} else {

Log.e(TAG, "권한 거부")
}

}

}

}


위와 같은 방법으로 카메라와 저장파일 접근 권한을 획득하시면 됩니다.


접근권한에 관한 설명은 이곳  을 참고해 주세요.




2. 카메라 프리뷰 연결 하기


MainActivity 에 카메라 프리뷰를 연결해 보겠습니다.~!



_activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >

<FrameLayout
android:id="@+id/cameraPreview"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>

</RelativeLayout>

FrameLayout 에 카메라 프리뷰를 연결합니다.



_MainActivity.kt

private var CAMERA_FACING = Camera.CameraInfo.CAMERA_FACING_FRONT

private var myCameraPreview: MyCameraPreview? = null

MainActivity 에 2개의 변수를 추가해 줍니다.


CAMERA_FACING 은 프리뷰에 보여질 카메라의 전면, 후면을 결정하는 변수입니다.


만약 후면을 사용하고 싶으신 분은 CAMERA_FACING = Camera.CameraInfo.CAMERA_FACING_BACK 변수로 변경해 주시면 됩니다.

private fun startCamera() {

Log.e(TAG, "startCamera")

// Create our Preview view and set it as the content of our activity.
myCameraPreview = MyCameraPreview(this, CAMERA_FACING)

cameraPreview.addView(myCameraPreview)

}

카메라 프리뷰를 설정하기 위한 startCamera() 함수를 추가해 줍니다.


xml 에서 설정한 FrameLayout 에 카메라 프리뷰를 addView 해줍니다.




_MyCameraPreview.java


이 부분은 자바 코드로 작성했습니다.


카메라 프리뷰의 가장 핵심이 되는 부분입니다. 안드로이드 기기 별로 카메라의 방향이 다른데 이에 맞춰 프리뷰를 설정할 수 있도록 했습니다.


public class MyCameraPreview extends SurfaceView implements SurfaceHolder.Callback {

private final String TAG = "MyTag";

private SurfaceHolder mHolder;

private int mCameraID;

private Camera mCamera;
private Camera.CameraInfo mCameraInfo;

private int mDisplayOrientation;

public MyCameraPreview(Context context, int cameraId) {
super(context);
Log.d(TAG, "MyCameraPreview cameraId : " + cameraId);

// 0 -> CAMERA_FACING_BACK
// 1 -> CAMERA_FACING_FRONT
mCameraID = cameraId;

try {
// attempt to get a Camera instance
mCamera = Camera.open(mCameraID);
} catch (Exception e) {
// Camera is not available (in use or does not exist)
Log.e(TAG, "Camera is not available");
}


// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
mHolder = getHolder();
mHolder.addCallback(this);

// deprecated setting, but required on Android versions prior to 3.0
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

// get display orientation
mDisplayOrientation = ((Activity)context).getWindowManager().getDefaultDisplay().getRotation();
}

public void surfaceCreated(SurfaceHolder holder) {
Log.d(TAG, "surfaceCreated");

// retrieve camera's info.
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
Camera.getCameraInfo(mCameraID, cameraInfo);

mCameraInfo = cameraInfo;

// The Surface has been created, now tell the camera where to draw the preview.
try {
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();

} catch (IOException e) {
Log.d(TAG, "Error setting camera preview: " + e.getMessage());
}
}

public void surfaceDestroyed(SurfaceHolder holder) {
Log.d(TAG, "surfaceDestroyed");
// empty. Take care of releasing the Camera preview in your activity.
}

public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
Log.d(TAG, "surfaceChanged");
// If your preview can change or rotate, take care of those events here.
// Make sure to stop the preview before resizing or reformatting it.

if (mHolder.getSurface() == null){
// preview surface does not exist
Log.e(TAG, "preview surface does not exist");
return;
}

// stop preview before making changes
try {
mCamera.stopPreview();
Log.d(TAG, "Preview stopped.");
} catch (Exception e) {
// ignore: tried to stop a non-existent preview
Log.d(TAG, "Error starting camera preview: " + e.getMessage());
}


// set preview size and make any resize, rotate or
// reformatting changes here
// start preview with new settings
int orientation = calculatePreviewOrientation(mCameraInfo, mDisplayOrientation);
mCamera.setDisplayOrientation(orientation);

try {
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
Log.d(TAG, "Camera preview started.");
} catch (Exception e) {
Log.d(TAG, "Error starting camera preview: " + e.getMessage());
}

}

/**
* 안드로이드 디바이스 방향에 맞는 카메라 프리뷰를 화면에 보여주기 위해 계산합니다.
*/
public int calculatePreviewOrientation(Camera.CameraInfo info, int rotation) {
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;

if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
result = (info.orientation + degrees) % 360;
result = (360 - result) % 360; // compensate the mirror
} else { // back-facing
result = (info.orientation - degrees + 360) % 360;
}

return result;
}

}

코드는 위와 같습니다.


MyCameraPreview 는 SurfaceView 를 상속하며 SurfaceHolder.Callback 인터페이스를 가지고 있습니다.


이에 대한 설명은 주석과 카메라 API 문서를 보면 자세히 나와있습니다.



1.  MyCameraPreview(Context context, in cameraId) 


생성자를 통해 Camera.open 을 합니다. 이때 cameraId 를 받아오는 데 이 값을 통해 전면과 후면 카메라를 결정합니다.



2. surfaceCreated -> surfaceChanged 순서로 동작합니다.



3. surfaceChanged 에서 아래 코드를 통해 안드로이드 디바이스 별 카메라의 회전 방향을 조정해 줍니다.

int orientation = calculatePreviewOrientation(mCameraInfo, mDisplayOrientation);
mCamera.setDisplayOrientation(orientation);



위와 같이 코드를 짜면 프리뷰 화면에 카메라를 띄우는데 성공하셨을 겁니다. 축하합니다.~!~!


프리뷰화면 캡처와 배경 이미지 적용은 다음 포스터에서 진행하겠습니다.



< 참고 사이트 >


구글 문서

카메라 캡처

카메라 방향 설정

반응형
Comments