怠慢プログラマーの備忘録

怠慢でナマケモノなプログラマーの備忘録です。

【Kotlin】カメラ機能の画質が悪い時の改善法[備忘録]

カメラ機能を実装する際、以前の記事のように

yutaabe200.hatenablog.com

val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE).apply {
            addCategory(Intent.CATEGORY_DEFAULT)
        }

startActivityForResult(intent, CAMERA_REQUEST_CODE)

で暗黙的インテントを使用すると取得できるBitmapデータが圧縮された状態で取得され画質(解像度)が低い状態です。

なので、撮影した画像のままのBitmapを取得する方法に変更する必要があります。

FilePropider

FileProvider  |  Android Developers

「content://」でアクセスする時に利用するのがFileProviderです。 FileProviderでアクセスすことにより、対象フォルダの一時許可が得られます。 (ちなみにAndroid Nからファイルアクセスで「file://」が、StrictModeの設定が変わり利用できなくなりました。)

propider_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="my_images" path="/" />
</paths>

カメラ/アルバム起動のパーミッション部分

AndroidManifest.xml

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

        <activity android:name=".presentation.activities.LicenseActivity" />
        <activity android:name=".presentation.activities.EditMapViewActivity"></activity>

        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="bio.enoque.enoque_android"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/provider_paths"
                />
        </provider>
   </application>
</manifest>

Adapter.kt(カメラ起動部)

    private fun setOnclickCaptureButton(holder: MarkPostImageCaptureRecyclerViewHolder) {
        holder.markPostCaptureButton.setOnClickListener {
            val items = arrayOf("カメラ", "アルバム")
            AlertDialog.Builder(this.activity)
                    .setTitle("選択してください")
                    .setItems(items, DialogInterface.OnClickListener { dialog, which ->
                        when (which) {
                        // Camera
                            0 -> {
                                Intent(MediaStore.ACTION_IMAGE_CAPTURE).resolveActivity(this.context.packageManager)?.let {
                                    if (this.checkPermission()) {
                                        this.takePicture()
                                    } else {
                                        this.grantCameraPermission()
                                        if (this.checkPermission()) {
                                            this.takePicture()
                                        }
                                    }
                                } ?: Toast.makeText(this.context, "カメラを扱うアプリがありません", Toast.LENGTH_LONG).show()
                            }
                            1 -> {
                                this.selectGallery()
                            }
                        }
                    })
                    .show()
        }
    }

Adapter.kt(パーミッション)

    // permission.CAMERAとpermission.WRITE_EXTERNAL_STORAGEが許可されているかどうかを判別
    private fun checkPermission(): Boolean {
        val cameraPermission = PackageManager.PERMISSION_GRANTED ==
                ContextCompat.checkSelfPermission(this.context, android.Manifest.permission.CAMERA)

        val extraStoragePermission = PackageManager.PERMISSION_GRANTED ==
                ContextCompat.checkSelfPermission(this.context, android.Manifest.permission.WRITE_EXTERNAL_STORAGE)

        return cameraPermission && extraStoragePermission
    }
    
    private fun grantCameraPermission() =
            ActivityCompat.requestPermissions(this.activity,
                    arrayOf(android.Manifest.permission.CAMERA, android.Manifest.permission.WRITE_EXTERNAL_STORAGE),
                    CAMERA_PERMISSION_REQUEST_CODE)

カメラ/アルバム起動部分

Adapter.kt(暗黙的インテント)

ここでは暗黙的インテントUriを渡します。

    private fun takePicture() {
        val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE).apply {
            addCategory(Intent.CATEGORY_DEFAULT)
            putExtra(MediaStore.EXTRA_OUTPUT, createSaveFileUri())
        }

        this.activity.startActivityForResult(intent, CAMERA_REQUEST_CODE)
    }

Adapter.kt(Uri生成)

    private fun createSaveFileUri(): Uri {
        val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.JAPAN).format(Date())
        val imageFileName = "casalack_" + timeStamp

        val storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM + "/casalack")
        if (!storageDir.exists()) {
            storageDir.mkdir()
        }
        val file = File.createTempFile(
                imageFileName, /* prefix */
                ".jpg", /* suffix */
                storageDir      /* directory */
        )
        
        // this.pathはStringのグローバル変数
        this.path = file.absolutePath

        return FileProvider.getUriForFile(this.activity, "bio.enoque.enoque_android", file)
    }

カメラ/アルバムの画像(bitmap)受取部分

  • ActivityのonActivityResultで受け取り、そのままAdpterに渡します
  • bitmapをリサイズしているのはOutOfMemory対策です

yutaabe200.hatenablog.com

Activity.kt

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        this.adapter?.let {
            it.onActivityResult(requestCode, resultCode, data)
        }
    }

Adapter.kt

companion object {
        const val CAMERA_REQUEST_CODE = 1
        const val CAMERA_PERMISSION_REQUEST_CODE = 2
        const val RESULT_PICK_IMAGEFILE = 1000
}

    fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        if (requestCode == CAMERA_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
            val contentValues = ContentValues().apply {
                put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
                put("_data", path)
            }
            this.activity.contentResolver.insert(
                    MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)


            val inputStream = FileInputStream(File(path))
            val bitmap = BitmapFactory.decodeStream(inputStream)
            val width = bitmap.width / 7
            val height = bitmap.height / 7
            val resizeBitmap = Bitmap.createScaledBitmap(bitmap, width, height, true)
            this.markPostImageCaptureRecyclerHolder?.let {
                it.markPostImageView.setImageBitmap(resizeBitmap)
            }

        } else {
            if (resultCode != 0) {
                val uri = data?.getData()
                val inputStream = this.activity.contentResolver?.openInputStream(uri)
                val bitmap: Bitmap? = BitmapFactory.decodeStream(inputStream)
                bitmap?.let { bp ->
                    val width = bp.width / 7
                    val height = bp.height / 7
                    val resizeBitmap = Bitmap.createScaledBitmap(bp, width, height, true)
                    this.markPostImageCaptureRecyclerHolder?.let {
                        it.markPostImageView.setImageBitmap(resizeBitmap)
                    }
                }
            }
        }
    }

Kotlinスタートブック -新しいAndroidプログラミング

Kotlinスタートブック -新しいAndroidプログラミング

Kotlinイン・アクション

Kotlinイン・アクション