안드로이드 개발자의 창고
[52일차 Android] 사진 촬영하기 본문
출처 : 안드로이드 앱스쿨 2기 윤재성 강사님 수업 PPT
📖 사진 촬영하기
- 카메라 기능이 구현되어 있는 Activity를 실행하여 사진을 촬영할 수 있다.
- 만약 카메라 기능이 구현되어 있는 애플리케이션이 다수 설치되어 있다면 앱을 선택하면 된다.
- 기본 카메라 사용하는 방법을 사용하면 사진 원본이 아닌 썸네일 이미지를 가져오게 된다.
- 촬영된 사진의 원본을 가져오려면 촬영된 사진을 파일로 저장한 다음 파일로부터 이미지 데이터를 가져오는 방식으로 개발해야 한다.
예제 코드
AndroidManifest.xml
<application
...
<!-- 촬영된 사진을 저장하는 프로바이더 -->
<provider
android:authorities="com.test.getpicture.file_provider"
android:name="androidx.core.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_path"/>
</provider>
...
xml / file_path.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path
name="storage/emulated/0"
path="."/>
</paths>
MainActivity.kt
class MainActivity : AppCompatActivity() {
lateinit var activityMainBinding: ActivityMainBinding
lateinit var cameraLauncher: ActivityResultLauncher<Intent>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activityMainBinding = ActivityMainBinding.inflate(layoutInflater)
// 사진 활영을 위한 런처
val contract1 = ActivityResultContracts.StartActivityForResult()
// 아래의 코드는 썸네일을 가져온다.(사진이 흐릿함)
cameraLauncher = registerForActivityResult(contract1){
// 사진을 촬영하고 촬영한 사진을 선택하고 돌아왔을 경우
if(it?.resultCode == RESULT_OK){
// Intent로부터 사진 데이터를 가져온다.
// 안드로이드 버전 별로 분기
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val bitmap = it.data?.getParcelableExtra("data", Bitmap::class.java)!!
activityMainBinding.imageView.setImageBitmap(bitmap)
} else {
val bitmap = it.data?.getParcelableExtra<Bitmap>("data")!!
activityMainBinding.imageView.setImageBitmap(bitmap)
}
}
}
activityMainBinding.run {
// 아래의 코드는 썸네일을 가져온다.(사진이 흐릿함)
button.setOnClickListener {
// 사진 활영을 위한 Activity를 실행한다.
val newIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
cameraLauncher.launch(newIntent)
}
}
setContentView(activityMainBinding.root)
}
}
결과
- 사진이 흐릿하며 실제 기기에서 테스트하면 사진이 회전되어 나타난다.
📖 사진 보정하기
- ImageView는 특정 크기보다 큰 이미지는 표현할 수 없으며 이 크기는 단말기마다 다르다.
- 이미지의 용량이 너무 크면 서버와 송수신할 때 데이터를 너무 많이 사용하게 된다.
- 스마트폰을 세워서 촬영할 경우 사진이 90도로 회전되어 있다.
- 이와 같은 이유로 카메라로 촬영된 사진은 보정해야 한다.
예제 코드
xml/file_path.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 실제 단말기에서도 동일한 경로 -->
<external-path
name="storage/emulated/0"
path="."/>
</paths>
MainActivity.kt
class MainActivity : AppCompatActivity() {
lateinit var activityMainBinding: ActivityMainBinding
lateinit var cameraLauncher: ActivityResultLauncher<Intent>
// 이미지가 저장될 위치
lateinit var filePath : String
// 저장된 파일에 접근하기 위한 Uri(import android.uri)
lateinit var contentUri:Uri
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activityMainBinding = ActivityMainBinding.inflate(layoutInflater)
// 애플리케이션을 위한 외부 저장소 경로를 가져온다.
// xml/file_path.xml에 등록한 경로를 가져온다.
filePath = getExternalFilesDir(null).toString()
// 사진 촬영을 위한 런처
val contract1 = ActivityResultContracts.StartActivityForResult()
cameraLauncher = registerForActivityResult(contract1){
if (it?.resultCode == RESULT_OK){
// uri를 이용해 이미지에 접근하여 Bitmap 객체로 생성한다.
val bitmap = BitmapFactory.decodeFile(contentUri.path)
// 이미지의 크기롤 조정한다.
// 이미지의 축소/확대 비율을 구한다.
val ratio = 1024.0 / bitmap.width
// 세로 길이를 구한다.
val targetHeight = (bitmap.height * ratio).toInt()
// 크기를 조정한 Bitmap을 생성한다.
val bitmap2 = Bitmap.createScaledBitmap(bitmap, 1024, targetHeight, false)
// 회전 각도를 가져온다.
val degree = getDegree(contentUri)
// 회전 이미지를 생성하기 위한 변환 행렬
val matrix = Matrix()
matrix.postRotate(degree.toFloat())
// 회전 행렬을 적용하여 회전된 이미지를 생성한다.
// 원본 이미지, 원본 이미지에서의 X좌표, 원본 이미지에서의 Y좌표, 원본 가로 길이, 원본 세로 길이, 변환행렬, 필터 정보
// 원본 이미지에서 지정된 x, y 좌표를 찍고 지정된 가로 세로 길이만큼의 이미지 데이터를 가져와 변환 행렬을 적용하여 이미지를 변환한다.
val bitmap3 = Bitmap.createBitmap(bitmap2, 0,0, bitmap2.width, bitmap2.height, matrix, false)
activityMainBinding.imageView.setImageBitmap(bitmap3)
// 이미지 파일은 삭제한다.
val file = File(contentUri.path)
file.delete()
}
}
activityMainBinding.run {
button.setOnClickListener {
val newIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
// 촬영될 사진이 저장될 파일 이름
val now = System.currentTimeMillis()
val fileName = "/temp_${now}.jpg"
// 경로 + 파일 이름
val picPath = "${filePath}/${fileName}"
val file = File(picPath)
// 사진이 저장될 경로를 관리할 uri 객체를 생성한다.
contentUri = FileProvider.getUriForFile(this@MainActivity, "com.test.getpicture.file_provider", file)
newIntent.putExtra(MediaStore.EXTRA_OUTPUT, contentUri)
cameraLauncher.launch(newIntent)
}
}
setContentView(activityMainBinding.root)
}
// 이미지 파일에 기록되어 있는 회전 정보를 가져온다.
fun getDegree(uri:Uri) : Int{
var exifInterface:ExifInterface? = null
// 사진 파일로부터 tag 정보를 관리하는 객체를 추출한다.
// 안드로이드 버전별 분기
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
val photoUri = MediaStore.setRequireOriginal(uri)
// 스트림 추출
val inputStream = contentResolver.openInputStream(photoUri)
// ExifInterface 정보를 읽어 온다.
exifInterface = ExifInterface(inputStream!!)
} else {
exifInterface = ExifInterface(uri.path!!)
}
var degree = 0
if (exifInterface != null){
// 각도 값을 가지고 온다.
val orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1)
when(orientation){
ExifInterface.ORIENTATION_ROTATE_90 -> degree = 90
ExifInterface.ORIENTATION_ROTATE_180 -> degree = 180
ExifInterface.ORIENTATION_ROTATE_270 -> degree = 270
}
}
return degree
}
}
결과
'Computer > Android' 카테고리의 다른 글
[52일차 Android] Socket 통신 (0) | 2023.07.27 |
---|---|
[52일차 Android] 앨범에서 사진 가져오기 (0) | 2023.07.24 |
[51일차 Android] Location (0) | 2023.07.20 |
[52일차 Android] Sensor (0) | 2023.07.20 |
[51일차 Android] Device Information(단말기 정보 파악) (0) | 2023.07.19 |