Android 앱에서 카메라로 사진을 찍어 가져오거나, 갤러리에 저장된 사진을 가져오는 방법을 소개하려 한다.
먼저 카메라 사용을 위해서는 권한 획득이 필요하다.
필요권한은 아래와 같다.
android.permission.CAMERA
android.permission.WRITE_EXTERNAL_STORAGE
android.permission.READ_EXTERNAL_STORAGE
위의 권한을 AndroidManifest.xml에 추가해준 후, 카메라 권한 및 외부 저장소 권한의 경우 마시멜로 이후 (API 23, Android 6.0) 사용자에게 직접 권한을 요청해야 한다.
관련 내용은 아래의 Android Developer 에서 자세하게 확인할 수 있다.
https://developer.android.com/training/permissions/requesting?hl=ko
권한 획득을 완료하였으면 이제 Intent를 사용해 외부 앱과 연동하는 것이 필요하다.
연동해야 할 앱이 카메라와 갤러리 이므로 해당되는 각각의 Intent를 선언 후 선택할 수 있게 사용자에게 제공해주면 된다. 아래는 예시 코드이다.
companion object {
private const val IMG_MIME_TYPE = "image/*"
}
private var mCameraPhotoPath: Uri? = null
...
private fun imageChooser(requestCode: Int) {
val pictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE).apply {
resolveActivity(packageManager)?.run {
createImageFile()?.let {
if (fileUri != null) {
putExtra(MediaStore.EXTRA_OUTPUT, fileUri)
}
}
}
}
val selectionIntent = Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = IMG_MIME_TYPE
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
}
val chooserIntent = Intent(Intent.ACTION_CHOOSER).apply {
putExtra(Intent.EXTRA_INTENT, selectionIntent)
putExtra(Intent.EXTRA_TITLE, "Choose action type")
putExtra(Intent.EXTRA_INITIAL_INTENTS, arrayOf(pictureIntent))
}
startActivityForResult(chooserIntent, requestCode)
}
private fun createImageFile(): Uri? {
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.MIME_TYPE, IMG_MIME_TYPE)
}
val resolver = applicationContext.contentResolver
return resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
.also { uri ->
mCameraPhotoPath = uri
}
}
fun imageChooser(requestCode: Int)
카메라앱으로 찍은 사진 혹은 갤러리에서 사진을 가져오는 Intent를 구성 메서드
Intent.EXTRA_ALLOW_MULTIPLE : 갤러리에서 사진 여러개 선택하여 처리할 수 있도록 허용여부 설정 가능, Boolean 값으로 설정, 기본값은 false
fun createImageFile(): Uri?
카메라앱 연동시 촬영한 사진 uri 정보를 가져오는 메서드
카메라앱 같은 경우, 기본 카메라앱은 촬영한 임시 이미지를 저장할 위치를 지정해줘야 한다. 이때 ContentResolver를 활용하여 저장될 경로를 지정해 주며, MeadiaStore.EXTRA_OUTPUT의 경우 onActivityResult()에서 data가 null로 들어오기 때문에 별도의 맴버 변수를 선언해 해당 경로를 저장한 후 처리해야 한다.
아래는 MediaStore.EXTRA_OUTPUT에 대한 참고 링크이다.
위에서 전달한 Intent의 requestCode로 onActivityResult()에서 해당 데이터를 처리할 수 있다.
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
LOAD_IMG_VIEW_REQUEST_CODE -> {
thread {
val uriArr = getResultUriArray(data)
runOnUiThread {
uriArr?.forEach { uri ->
// URI로 가져온 이미지 활용 코드 작성
}
}
}
}
}
}
private fun getResultUriArray(data: Intent?): Array<Uri>? {
val uriList = mutableListOf<Uri>()
if (data?.data != null) {
data.data?.let { uri ->
if (!uri.toString().startsWith("content://"))
uriList.add(Uri.fromFile(File(uri.toString())))
else
uriList.add(uri)
}
} else if (data?.clipData != null) {
data.clipData?.let { clipData ->
for (i in 0 until clipData.itemCount) {
uriList.add(clipData.getItemAt(i).uri)
}
}
} else {
if (mCameraPhotoPath != null) {
uriList.add(mCameraPhotoPath!!)
}
}
val results = getResizedFileList(uriList, 80)
return if (results.isEmpty()) null else results.toTypedArray()
}
private fun getResizedFileList(uriList: List<Uri>, quality: Int): List<Uri> {
val results = mutableListOf<Uri>()
val dirPath = "${cacheDir}/Capture/"
File(dirPath).let { dirs ->
if (!dirs.exists()) {
dirs.mkdirs()
}
dirs.listFiles()?.forEach { file ->
file?.run {
if (System.currentTimeMillis() - file.lastModified() >= TEMP_IMG_REMOVE_INTERVAL) {
file.delete()
}
}
}
}
uriList.forEach { uri ->
try {
getRotatedBitmap(uri)?.let { bitmap ->
val fileName = "tmp_img_${System.currentTimeMillis()}.jpg"
val file = File("$dirPath$fileName")
file.outputStream().use { fos ->
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, fos)
}
bitmap.recycle()
results.add(Uri.fromFile(file))
}
} catch (e: Exception) {
e.printStackTrace()
} finally {
if (uri == mCameraPhotoPath) {
applicationContext.contentResolver.delete(uri, null, null)
mCameraPhotoPath = null
}
}
}
return results
}
private fun getFilePathFromUri(uri: Uri): String? {
var path: String? = null
val contentResolver = applicationContext.contentResolver
contentResolver.query(uri, null, null, null, null)?.use { cursor ->
cursor.moveToNext()
val pathColumnIdx = cursor.getColumnIndex("_data")
if (pathColumnIdx != -1) {
path = cursor.getString(pathColumnIdx)
} else {
val idColumnIdx = cursor.getColumnIndex("document_id")
if (idColumnIdx != -1) {
val documentId = cursor.getString(idColumnIdx)
val contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
val selection = "_id = ?"
val selectionArgs = arrayOf(documentId.split(':')[1])
contentResolver.query(contentUri, null, selection, selectionArgs, null)
?.use { cursor2 ->
cursor2.moveToNext()
val pathColumnIdx2 = cursor2.getColumnIndex("_data")
if (pathColumnIdx2 != -1)
path = cursor2.getString(pathColumnIdx2)
}
}
}
}
return path
}
private fun getRotatedBitmap(uri: Uri): Bitmap? {
contentResolver.openFileDescriptor(uri, "r")?.fileDescriptor?.let {
var bitmap = BitmapFactory.decodeFileDescriptor(it)
getFilePathFromUri(uri)?.let { path ->
ExifInterface(path).run {
val orientation =
getAttributeInt(
ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_NORMAL
)
val degrees = when (orientation) {
ExifInterface.ORIENTATION_ROTATE_90 -> 90f
ExifInterface.ORIENTATION_ROTATE_180 -> 180f
ExifInterface.ORIENTATION_ROTATE_270 -> 270f
else -> 0f
}
if (degrees != 0f && bitmap != null) {
val matrix = Matrix().apply {
setRotate(degrees)
}
val converted = Bitmap.createBitmap(
bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true
)
if (converted != bitmap) {
bitmap.recycle()
bitmap = converted
}
}
}
return bitmap
}
}
return null
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?)
Activity에서 요청한 requestCode에 대한 결과값을 처리하는 메서드로 요청한 데이터를 Intent로 받아 처리할 수 있다.
fun getResultUriArray(data: Intent?): Array<Uri>?
onActivityResult()에서 받은 Intent 데이터를 확인해 Uri 배열을 반환한다.
fun getResizedFileList(uriList: List<Uri>, quality: Int): Array<Uri>
사진 파일의 크기를 변경하는 메서드
fun getFilePathFromUri(uri: Uri): String?
URI 데이터에 있는 경로를 가져오는 메서드
fun getRotatedBitmap(uri: Uri): Bitmap?
사진촬영 시 기기 회전값을 확인해 사용자가 찍은 화면으로 회전 각도를 적용하여 Bitmap을 반환해주는 메서드
사진앱에서 촬영하면 핸드폰의 각도에 따라 사진의 회전이 보정되어 화면에 표시된다. 이는 사진에 포함된 정보에 회전 각도도 포함되기 때문인데 이미지를 그대로 불러올 경우 회전이 적용되지 않는 현상이 발생한다. 따라서 촬영했던 그대로 보여주기 위해 작업이 필요하다.
'안드로이드' 카테고리의 다른 글
Android EditText 입력창(SoftInput) 숨기는 방법 (0) | 2020.03.16 |
---|---|
Android Asset 활용하기 (0) | 2020.03.10 |
다른 앱의 알림(Notification) 내용 가져오는 방법(알림 접근) (1) | 2020.03.09 |
RxJava - (1) 소개 및 초기 설정 (0) | 2020.02.25 |
기상청 API 변경사항 적용 및 후기 (0) | 2020.02.12 |