{"id":1237,"date":"2019-05-11T22:32:22","date_gmt":"2019-05-11T13:32:22","guid":{"rendered":"https:\/\/www.charlezz.com\/?p=1237"},"modified":"2019-12-21T16:48:53","modified_gmt":"2019-12-21T07:48:53","slug":"android-camerax-%eb%9d%bc%ec%9d%b4%eb%b8%8c%eb%9f%ac%eb%a6%ac-%eb%af%b8%eb%a6%ac%eb%b3%b4%ea%b8%b0","status":"publish","type":"post","link":"https:\/\/charlezz.com\/?p=1237","title":{"rendered":"Android CameraX \ub77c\uc774\ube0c\ub7ec\ub9ac \ubbf8\ub9ac\ubcf4\uae30"},"content":{"rendered":"<pre class=\"\">\uc544\uc9c1 \uc54c\ud30c \ub2e8\uacc4\uc758 \ub77c\uc774\ube0c\ub7ec\ub9ac \uc785\ub2c8\ub2e4<\/pre>\n<p><a href=\"https:\/\/www.charlezz.com\/?attachment_id=1244\" rel=\"attachment wp-att-1244\"><img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/www.charlezz.com\/wordpress\/wp-content\/uploads\/2019\/05\/1_XfaPtGQEi34X8kfU9RXu0Q-1024x364.png\" alt=\"\" width=\"750\" height=\"267\" class=\"size-large wp-image-1244 alignnone\" srcset=\"https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2019\/05\/1_XfaPtGQEi34X8kfU9RXu0Q-1024x364.png 1024w, https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2019\/05\/1_XfaPtGQEi34X8kfU9RXu0Q-300x107.png 300w, https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2019\/05\/1_XfaPtGQEi34X8kfU9RXu0Q-768x273.png 768w, https:\/\/charlezz.com\/wordpress\/wp-content\/uploads\/2019\/05\/1_XfaPtGQEi34X8kfU9RXu0Q.png 1600w\" sizes=\"(max-width: 750px) 100vw, 750px\" \/><\/a><\/p>\n<p><iframe loading=\"lazy\" width=\"594\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/QYkTXJ2TuiA\" frameborder=\"0\" allow=\"accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen=\"allowfullscreen\"><\/iframe><\/p>\n<p>CameraX\ub294 Jetpack\uc5d0 \ud3ec\ud568\ub41c \uc11c\ud3ec\ud2b8 \ub77c\uc774\ube0c\ub7ec\ub9ac\ub85c \uce74\uba54\ub77c \uc571 \uac1c\ubc1c\uc744 \uc27d\uac8c \ud560 \uc218 \uc788\ub3c4\ub85d \ub3c4\uc640 \uc90d\ub2c8\ub2e4.<\/p>\n<h2>\ud2b9\uc9d5<\/h2>\n<ul>\n<li>Camera2\ub97c \uc0ac\uc6a9\ud558\ubbc0\ub85c Android 5.0 (API level 21)\uae4c\uc9c0\ub9cc \uc9c0\uc6d0<\/li>\n<li>\uc720\uc988\ucf00\uc774\uc2a4 \uae30\ubc18\uc73c\ub85c \uc124\uacc4\ub418\uc5b4 Preview, Image Processing, Image Capture \uc720\uc988 \ucf00\uc774\uc2a4 \ub3d9\uc2dc \uc9c0\uc6d0<\/li>\n<li>\uc0dd\uba85\uc8fc\uae30\ub97c \uc778\uc2dd\ud568<\/li>\n<li>\uc7a5\uce58 \ud638\ud658\uc131 \ubb38\uc81c \ud574\uacb0\ud568\uc73c\ub85c \uae30\uae30\ubcc4 \ubd84\uae30\ucf54\ub4dc \uac10\uc18c<\/li>\n<li>\ud2b9\uc815 \ub514\ubc14\uc774\uc2a4\uc5d0 \uc885\uc18d\ub418\ub294 Bokeh, HDR \ub4f1\uacfc \uac19\uc740 \uc774\ud399\ud2b8 \uc9c0\uc6d0\u00a0<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<h2>\ucd5c\uc18c \uc694\uad6c \uc0ac\ud56d<\/h2>\n<p>CameraX \ub77c\uc774\ube0c\ub7ec\ub9ac\ub97c \uc0ac\uc6a9\ud558\uae30 \uc704\ud574\uc11c\ub294 \ub2e4\uc74c\uacfc \uac19\uc740 \uc870\uac74\uc744 \ub9cc\uc871\ud574\uc57c\ud569\ub2c8\ub2e4.<\/p>\n<ul>\n<li>Android API Level 21 (\ub864\ub9ac\ud31d 5.0)<\/li>\n<li>\uc548\ub4dc\ub85c\uc774\ub4dc \uc544\ud0a4\ud14d\ucc98 \ucef4\ud3ec\ub10c\ud2b8 1.1.1 \ubc84\uc804<\/li>\n<li>lifecycle-aware\uc561\ud2f0\ube44\ud2f0\ub85c\ub294 FragmentActivity \ub610\ub294\u00a0 AppCompatActivity \uc0ac\uc6a9<\/li>\n<\/ul>\n<h2>\uce74\uba54\ub77c \uc0d8\ud50c \ud504\ub85c\uc81d\ud2b8 \uc571 \ub9cc\ub4e4\uc5b4 \ubcf4\uae30<\/h2>\n<p>CameraX\ub97c \uc774\uc6a9\ud558\uc5ec \ubbf8\ub9ac\ubcf4\uae30, \uc0ac\uc9c4\ucc0d\uae30,\uc774\ubbf8\uc9c0 \ud504\ub85c\uc138\uc2f1 3\uac00\uc9c0 \uc720\uc988\ucf00\uc774\uc2a4\ub97c \ud3ec\ud568\ud558\ub294 \uc0d8\ud50c \uc571\uc744 \ub9cc\ub4e4\uc5b4 \ubcf4\ub3c4\ub85d \ud558\uaca0\uc2b5\ub2c8\ub2e4. \uba3c\uc800 \uc548\ub4dc\ub85c\uc774\ub4dc \uc2a4\ud29c\ub514\uc624\ub97c \uc5f4\uace0 \uc0c8\ub85c\uc6b4 \ud504\ub85c\uc81d\ud2b8\ub97c \ub9cc\ub4e4\uc5b4 \uc8fc\uc138\uc694.<\/p>\n<h3>\uc758\uc874\uc131 \ucd94\uac00\ud558\uae30<\/h3>\n<p>CameraX\ub77c\uc774\ube0c\ub7ec\ub9ac\ub97c \uc0ac\uc6a9\ud558\uae30 \uc704\ud574\uc11c\ub294 Google Maven \uc800\uc7a5\uc18c\ub97c \ud504\ub85c\uc81d\ud2b8\uc5d0 \ucd94\uac00\ud574\uc57c\ud569\ub2c8\ub2e4.<br \/>\n<strong>\ud504\ub85c\uc81d\ud2b8 \ub808\ubca8\uc758 build.gradle<\/strong>\uc744 \uc5f4\uc5b4 <strong>google()<\/strong> \ub9ac\ud30c\uc9c0\ud1a0\ub9ac\ub97c \ub2e4\uc74c\uacfc \uac19\uc774 \ucd94\uac00\ud569\ub2c8\ub2e4.<\/p>\n<pre class=\"marking:true lang:java mark:3 range:3 decode:true\">allprojects {\r\n    repositories {\r\n        google()\r\n        jcenter()\r\n    }\r\n}<\/pre>\n<p>\ubaa8\ub4c8\ub808\ubca8\uc758 <strong>build.gradle<\/strong>\uc5d0\ub3c4 \ub2e4\uc74c\uc744 \ucd94\uac00\ud569\ub2c8\ub2e4.<\/p>\n<pre class=\"lang:java decode:true\">dependencies {\r\n    \/\/ CameraX \ucf54\uc5b4\ub77c\uc774\ube0c\ub7ec\ub9ac\ub97c \ucd94\uac00\ud569\ub2c8\ub2e4.\r\n    def camerax_version = \"1.0.0-alpha01\"\r\n    implementation \"androidx.camera:camera-core:${camerax_version}\"\r\n    \/\/ Camera2 extensions\uc744 \uc0ac\uc6a9\ud558\uace0 \uc2f6\ub2e4\uba74 \uc544\ub798\ub0b4\uc6a9\ub3c4 \ucd94\uac00\ud574\uc8fc\uc138\uc694\r\n    implementation \"androidx.camera:camera-camera2:${camerax_version}\"\r\n\r\n}<\/pre>\n<p>\ucd5c\uc2e0\ubc84\uc804\uc758 CameraX\ub97c \ud655\uc778\ud558\uc2dc\ub824\uba74 <a href=\"https:\/\/developer.android.com\/training\/camerax\/architecture\">\ub9c1\ud06c<\/a>\ub97c \ud074\ub9ad\ud574\uc8fc\uc138\uc694<\/p>\n<h3>\ubbf8\ub9ac\ubcf4\uae30 \ubdf0 \ub9cc\ub4e4\uae30(ViewFinder)<\/h3>\n<p>\uad6c\uae00 \uacf5\uc2dd \uce74\uba54\ub77cX \uc608\uc81c\uc5d0\uc11c\ub294 ViewFinder(\ubdf0\ud30c\uc778\ub354)\ub77c\ub294 \uba85\uce6d\uc744 \uc4f0\ub124\uc694. \uc800\ub3c4 \ubdf0\ud30c\uc778\ub354\ub77c\uace0 \ud558\uaca0\uc2b5\ub2c8\ub2e4.<br \/>\n\ubdf0\ud30c\uc778\ub354\ub85c \ub9cc\ub4e4 View\ub294 TextureView\ub85c \ud558\uaca0\uc2b5\ub2c8\ub2e4.<\/p>\n<p><em>Note : TextureView\ub97c \uc0ac\uc6a9\ud558\ub294 \uc774\uc720\ub294 \uce74\uba54\ub77c\ub85c \ubd80\ud130 \uc5bb\ub294 \ucf58\ud150\uce20 \uc2a4\ud2b8\ub9bc\uc744 \ud45c\ud604\ud560 \uc218 \uc788\ub294 \uc801\uc808\ud55c \ubdf0\uc774\uae30 \ub54c\ubb38\uc785\ub2c8\ub2e4. Android\uc5d0\uc11c\ub294 \uc774\ub7ec\ud55c \uc2a4\ud2b8\ub9bc\uc744 SurfaceTexture\ub85c \ub2e4\ub8f9\ub2c8\ub2e4. \uc880 \ub354 \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 <a href=\"https:\/\/www.charlezz.com\/?p=1025\">\uc548\ub4dc\ub85c\uc774\ub4dc \uadf8\ub798\ud53d \uc2dc\uc2a4\ud15c<\/a>\u00a0\ubc0f <a href=\"https:\/\/www.charlezz.com\/?p=1118\">\uce74\uba54\ub77c2 Basic \uc18c\uc2a4 \ubd84\uc11d<\/a> \ud3b8\uc744 \ucc38\uace0\ud574\uc8fc\uc138\uc694<\/em><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/codelabs.developers.google.com\/codelabs\/camerax-getting-started\/img\/f0092805d8ee149e.png\" \/><\/p>\n<pre class=\"lang:xhtml decode:true \">&lt;?xml version=\"1.0\" encoding=\"utf-8\"?&gt;\r\n&lt;androidx.constraintlayout.widget.ConstraintLayout\r\n        xmlns:android=\"http:\/\/schemas.android.com\/apk\/res\/android\"\r\n        xmlns:tools=\"http:\/\/schemas.android.com\/tools\"\r\n        xmlns:app=\"http:\/\/schemas.android.com\/apk\/res-auto\"\r\n        android:layout_width=\"match_parent\"\r\n        android:layout_height=\"match_parent\"\r\n        tools:context=\".MainActivity\"&gt;\r\n\r\n    &lt;TextureView\r\n            android:id=\"@+id\/view_finder\"\r\n            android:layout_width=\"640px\"\r\n            android:layout_height=\"640px\"\r\n            app:layout_constraintTop_toTopOf=\"parent\"\r\n            app:layout_constraintBottom_toBottomOf=\"parent\"\r\n            app:layout_constraintStart_toStartOf=\"parent\"\r\n            app:layout_constraintEnd_toEndOf=\"parent\" \/&gt;\r\n\r\n&lt;\/androidx.constraintlayout.widget.ConstraintLayout&gt;<\/pre>\n<h3>\uad8c\ud55c \uc694\uccad\ud558\uae30<\/h3>\n<p>\uc548\ub4dc\ub85c\uc774\ub4dc \uc2dc\uc2a4\ud15c\uc5d0\uc11c \uce74\uba54\ub77c\ub97c \uc0ac\uc6a9\ud558\uae30 \uc704\ud574\uc11c\ub294 \ubc18\ub4dc\uc2dc \uad8c\ud55c\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. \ub9e4\ub2c8\ud398\uc2a4\ud2b8\uc5d0 \ub2e4\uc74c\uacfc \uac19\uc774 \uc120\uc5b8\ud574\uc90d\ub2c8\ub2e4.<\/p>\n<pre class=\"lang:xhtml decode:true \">&lt;uses-permission android:name=\"android.permission.CAMERA\" \/&gt;<\/pre>\n<p>MainActivity \ud074\ub798\uc2a4\uc5d0 \ub2e4\uc74c\uacfc \uac19\uc740 \uc0c1\uc218\ub97c \uc120\uc5b8\ud569\ub2c8\ub2e4.<\/p>\n<pre class=\"lang:java decode:true\">private const val REQUEST_CODE_PERMISSIONS = 10\r\nprivate val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA)<\/pre>\n<p><span>MainActivity \ub0b4\ubd80\uc5d0 \ub2e4\uc74c\uacfc \uac19\uc740 \ud544\ub4dc\uc640 \uba54\uc18c\ub4dc\ub97c \uc120\uc5b8\ud569\ub2c8\ub2e4. \uce74\uba54\ub77c\uc5d0 \ub300\ud55c \uad8c\ud55c\uc774 \uc694\uccad\ub418\uace0 \uad8c\ud55c\uc774 \uc2b9\uc778 \ub418\uba74\u00a0 startCamera()\ub97c \ud638\ucd9c\ud558\uac8c \ub429\ub2c8\ub2e4.<\/span><\/p>\n<pre class=\"lang:java decode:true \">class MainActivity : AppCompatActivity() {\r\n\r\n    override fun onCreate(savedInstanceState: Bundle?) {\r\n        ...\r\n    }\r\n\r\n    private lateinit var viewFinder: TextureView\r\n\r\n    private fun startCamera() {\r\n        ...\r\n    }\r\n\r\n    private fun updateTransform() {\r\n        ...\r\n    }\r\n\r\n    \/**\r\n     * \uad8c\ud55c\uc774 \uc2b9\uc778 \ub418\uba74 \uce74\uba54\ub77c\ub97c \uc5f4\uace0, \uc544\ub2c8\uba74 \ud1a0\uc2a4\ud2b8\ub97c \ub744\uc6b0\uace0 \uc561\ud2f0\ube44\ud2f0\ub97c \uc885\ub8cc\ud569\ub2c8\ub2e4\r\n     *\/\r\n    override fun onRequestPermissionsResult(\r\n        requestCode: Int, permissions: Array&lt;String&gt;, grantResults: IntArray) {\r\n        if (requestCode == REQUEST_CODE_PERMISSIONS) {\r\n            if (allPermissionsGranted()) {\r\n                viewFinder.post { startCamera() }\r\n            } else {\r\n                Toast.makeText(this,\r\n                    \"Permissions not granted by the user.\", \r\n                    Toast.LENGTH_SHORT).show()\r\n                finish()\r\n            }\r\n        }\r\n    }\r\n\r\n    \/**\r\n     * \uc120\uc5b8\ub41c \ubaa8\ub4e0 \uad8c\ud55c\uc744 \uccb4\ud06c \ud569\ub2c8\ub2e4.\r\n     *\/\r\n    private fun allPermissionsGranted(): Boolean {\r\n        for (permission in REQUIRED_PERMISSIONS) {\r\n            if (ContextCompat.checkSelfPermission(\r\n                    this, permission) != PackageManager.PERMISSION_GRANTED) {\r\n                return false\r\n            }\r\n        }\r\n        return true\r\n    }\r\n}<\/pre>\n<p>MainActivity\uc758 onCreate() \ub0b4\ubd80\ub97c \ubcf4\ub3c4\ub85d\ud558\uaca0\uc2b5\ub2c8\ub2e4.\u00a0<\/p>\n<pre class=\"lang:java decode:true\">override fun onCreate(savedInstanceState: Bundle?) {\r\n\r\n    viewFinder = findViewById(R.id.view_finder)\r\n\r\n    \/\/ \uce74\uba54\ub77c \uad8c\ud55c \uc694\uccad\ud568\r\n    if (allPermissionsGranted()) {\r\n        viewFinder.post { startCamera() }\r\n    } else {\r\n        ActivityCompat.requestPermissions(\r\n            this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)\r\n    }\r\n\r\n    \/\/ \ud14d\uc2a4\uccd0\ubdf0\uac00 \ubcc0\uacbd\ub420\ub54c\ub9c8\ub2e4 \ub808\uc774\uc544\uc6c3\uc744 \uc0c8\ub85c \uace0\uce69\ub2c8\ub2e4.\r\n    viewFinder.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -&gt;\r\n        updateTransform()\r\n    }\r\n}<\/pre>\n<p>\uc774\uc81c \uc571\uc744 \uc2dc\uc791\ud558\uba74 \uce74\uba54\ub77c \uad8c\ud55c\uc744 \uccb4\ud06c\ud558\uac8c \ub429\ub2c8\ub2e4. \uad8c\ud55c\uc774 \uc774\ubbf8 \uc2b9\uc778\ub41c\uc801\uc774 \uc788\ub2e4\uba74 startCamera()\ub97c \uc9c1\uc811\uc801\uc73c\ub85c \ud638\ucd9c\ud558\uac8c \ub420\uac83\uc774\uace0, \uadf8\ub807\uc9c0 \uc54a\ub2e4\uba74 \uad8c\ud55c\uc744 \uc694\uccad\ud558\uc5ec \uc2b9\uc778\ub418\uc5c8\uc744 \ub54c startCamera()\ub97c \ud638\ucd9c\ud569\ub2c8\ub2e4.<\/p>\n<p><em><strong>Note\u00a0<\/strong>:<b>\u00a0<\/b>\u00a0startCamera()\ub97c \uba54\uc778\uc4f0\ub808\ub4dc\uc5d0\uc11c \ud638\ucd9c\ud558\ub294 \ub300\uc2e0\uc5d0 viewFinder.post {&#8230;}\uc5d0\uc11c \ud638\ucd9c\ud588\ub294\ub370 \uc774\ub294 \ubdf0\ud30c\uc778\ub354\uac00 \ud655\uc2e4\ud788 \uc778\ud50c\ub808\uc774\ud305\uc774 \ub05d\ub09c\ub2f4\uc5d0 \ud638\ucd9c\ud558\uae30 \uc704\ud568\uc785\ub2c8\ub2e4.<\/em><\/p>\n<h3>\ubbf8\ub9ac\ubcf4\uae30(\ubdf0\ud30c\uc778\ub354) \uad6c\ud604\ud558\uae30<\/h3>\n<p>startCamera() \uba54\uc18c\ub4dc \ub0b4\uc5d0\uc11c CameraX\ub77c\uc774\ube0c\ub7ec\ub9ac\ub97c \ud1b5\ud574 \ubdf0\ud30c\uc778\ub354\ub97c \uad6c\ud604\ud574 \ubcf4\ub3c4\ub85d \ud558\uaca0\uc2b5\ub2c8\ub2e4.<\/p>\n<p><em><strong>Note<\/strong> :\u00a0<span class=\"\">\uc774 \uae00\uc744 \uc4f0\ub294 \uc2dc\uc810\uc5d0\uc11c\ub294\u00a0<\/span>\ud3ec\ud568\ub41c AppCompat \ub77c\uc774\ube0c\ub7ec\ub9ac\uc5d0 FragmentActivity\ub97c \uc0c1\uc18d\ubc1b\uc558\uc9c0\ub9cc LifecycleOwner\uac00 \uad6c\ud604\ub418\uc9c0 \uc54a\uc740 \uacbd\uc6b0 bindToLifecycle \uba54\uc11c\ub4dc \ud638\ucd9c\uc2dc \uc624\ub958\uac00 \ubc1c\uc0dd\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uadf8\ub807\ub2e4\uba74 \uae30\uc874 AppCompat \ub77c\uc774\ube0c\ub7ec\ub9ac\uc758 \uc885\uc18d\uc131\uc744 \ub2e4\uc74c\uacfc \uac19\uc774 \ubcc0\uacbd\ud574\uc8fc\uc138\uc694.<\/em><\/p>\n<pre class=\"lang:java decode:true \">implementation 'androidx.appcompat : appcompat : 1.1.0-alpha05'<\/pre>\n<pre class=\"lang:java decode:true\">private fun startCamera() {\r\n\r\n    \/\/ \ubbf8\ub9ac\ubcf4\uae30\ub97c \uc704\ud55c \ud658\uacbd\uc124\uc815 \uac1d\uccb4\ub97c \ub9cc\ub4ed\ub2c8\ub2e4.\r\n    val previewConfig = PreviewConfig.Builder().apply {\r\n        setTargetAspectRatio(Rational(1, 1))\r\n        setTargetResolution(Size(640, 640))\r\n    }.build()\r\n\r\n    \/\/ \ubbf8\ub9ac\ubcf4\uae30 \uac1d\uccb4\ub97c \ub9cc\ub4ed\ub2c8\ub2e4.\r\n    val preview = Preview(previewConfig)\r\n\r\n    \/\/ \ubdf0\ud30c\uc778\ub354\uac00 \uac31\uc2e0\ub420\ub54c \ub9c8\ub2e4 \ub808\uc774\uc544\uc6c3\uc744 \ub2e4\uc2dc \uc124\uc815\ud569\ub2c8\ub2e4.\r\n    preview.setOnPreviewOutputUpdateListener {\r\n        viewFinder.surfaceTexture = it.surfaceTexture\r\n        \/\/ SurfaceTexture\ub97c \ub118\uaca8\uc90c\uc73c\ub85c\uc368 \uce74\uba54\ub77c\uc601\uc0c1\uc774 \ubdf0\ud30c\uc778\ub354\uc5d0 \uc5f0\uacb0\ub429\ub2c8\ub2e4.\r\n        updateTransform()\r\n    }\r\n\r\n    \/\/ \uc0dd\uba85\uc8fc\uae30\uc5d0 \uce74\uba54\ub77c\ub97c \ubc14\uc774\ub529\ud569\ub2c8\ub2e4\r\n    CameraX.bindToLifecycle(this, preview)\r\n}<\/pre>\n<p>\uc774\uc81c updateTransform()\uc744 \uad6c\ud604\ud558\uc5ec \uae30\uae30\uc758 \ud68c\uc804\ubc29\ud5a5\uc5d0 \ub530\ub77c \uce74\uba54\ub77c \uc601\uc0c1\ub3c4 \uc54c\ub9de\uac8c \ub9e4\ud551 \ub420 \uc218 \uc788\ub3c4\ub85d \ud569\ub2c8\ub2e4.<\/p>\n<pre class=\"lang:java decode:true \">private fun updateTransform() {\r\n    val matrix = Matrix()\r\n\r\n    \/\/ \ubdf0\ud30c\uc778\ub354\uc758 \uc911\uc2ec\uc744 \uacc4\uc0b0\ud569\ub2c8\ub2e4.\r\n    val centerX = viewFinder.width \/ 2f\r\n    val centerY = viewFinder.height \/ 2f\r\n\r\n    \/\/ \ud654\uba74 \ud68c\uc804\uc744 \uc704\ud55c \ud68c\uc804\uac01\ub3c4\ub97c \ucd9c\ub825\ud569\ub2c8\ub2e4\r\n    val rotationDegrees = when(viewFinder.display.rotation) {\r\n        Surface.ROTATION_0 -&gt; 0\r\n        Surface.ROTATION_90 -&gt; 90\r\n        Surface.ROTATION_180 -&gt; 180\r\n        Surface.ROTATION_270 -&gt; 270\r\n        else -&gt; return\r\n    }\r\n    matrix.postRotate(-rotationDegrees.toFloat(), centerX, centerY)\r\n\r\n    \/\/ \ub9c8\uce68\ub0b4 \ubdf0\ud30c\uc778\ub354\uc758 \ubc29\ud5a5\uc774 \uc54c\ub9de\uac8c \ub098\ud0c0\ub0a9\ub2c8\ub2e4.\r\n    viewFinder.setTransform(matrix)\r\n}<\/pre>\n<p>\uc571\uc744 \uc774\uc81c \ube4c\ub4dc\ud558\uace0 \uc2e4\ud589\ud574\ubcf4\uc138\uc694. \uc62c\ubc14\ub974\uac8c \ubbf8\ub9ac\ubcf4\uae30\uac00 \ub098\ud0c0\ub098\ub294 \uac83\uc744 \ud655\uc778\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n<h3>\uc0ac\uc9c4 \ucc0d\uae30 \uad6c\ud604<\/h3>\n<p>activity_main.xml \uc5d0 \ub2e4\uc74c\uacfc \uac19\uc774 \uc0ac\uc9c4\uc744 \ucc0d\uae30 \uc704\ud55c \ubc84\ud2bc\uc744 \ucd94\uac00\ud569\ub2c8\ub2e4.<\/p>\n<pre class=\"lang:java decode:true \">&lt;ImageButton\r\n        android:id=\"@+id\/capture_button\"\r\n        android:layout_width=\"72dp\"\r\n        android:layout_height=\"72dp\"\r\n        android:layout_margin=\"24dp\"\r\n        app:srcCompat=\"@android:drawable\/ic_menu_camera\"\r\n        app:layout_constraintBottom_toBottomOf=\"parent\"\r\n        app:layout_constraintEnd_toEndOf=\"parent\"\r\n        app:layout_constraintStart_toStartOf=\"parent\" \/&gt;<\/pre>\n<p>\uc0ac\uc9c4\uc744 \ucc0d\uae30\uc704\ud574 startCamera()\uba54\uc18c\ub4dc\ub97c \uc870\uae08 \uc218\uc815\ud574\ubcf4\ub3c4\ub85d \ud558\uaca0\uc2b5\ub2c8\ub2e4.<\/p>\n<pre class=\"lang:java decode:true\">private fun startCamera() {\r\n        \/\/\ubbf8\ub9ac\ubcf4\uae30 \uc124\uc815 \uc2dc\uc791\r\n        val previewConfig = ...\r\n        val preview = ...\r\n        \/\/\ubbf8\ub9ac\ubcf4\uae30 \uc124\uc815 \ub05d\r\n\r\n        \/\/\uc0ac\uc9c4\ucc0d\uae30 \uc124\uc815 \uc2dc\uc791\r\n        \/\/\uc0ac\uc9c4\uc744 \ucc0d\uae30 \uc704\ud55c \uc124\uc815\uc744 \uc704\ud574 ImageCaptureConfig\ub97c \uc0dd\uc131\ud569\ub2c8\ub2e4.\r\n        val imageCaptureConfig = ImageCaptureConfig.Builder()\r\n            .apply {\r\n                setTargetAspectRatio(Rational(1, 1))\r\n                \/\/\uc0ac\uc9c4\uc758 \ud574\uc0c1\ub3c4\ub97c \uc9c1\uc811 \uc124\uc815\ud558\uc9c0 \uc54a\ub294 \ub300\uc2e0 \ud654\uba74 \ube44\uc728\uacfc \ucea1\uccd0\ubaa8\ub4dc\ub85c\r\n                \/\/CameraX\ub77c\uc774\ube0c\ub7ec\ub9ac\uac00 \uc801\ub2f9\ud55c \ud574\uc0c1\ub3c4\ub97c \uace0\ub974\uac8c \ud569\ub2c8\ub2e4.\r\n                setCaptureMode(ImageCapture.CaptureMode.MIN_LATENCY)\r\n            }.build()\r\n        \/\/ ImageCapture \uac1d\uccb4\ub97c \uc774\uc6a9\ud574 \ubc84\ud2bc\uc774 \ud074\ub9ad\ub418\uc5c8\uc744\ub54c \uc0ac\uc9c4\uc744 \ucc0d\ub3c4\ub85d \ud569\ub2c8\ub2e4.\r\n        val imageCapture = ImageCapture(imageCaptureConfig)\r\n        findViewById&lt;ImageButton&gt;(R.id.capture_button).setOnClickListener {\r\n            val file = File(externalMediaDirs.first(),\r\n                \"${System.currentTimeMillis()}.jpg\")\r\n            imageCapture.takePicture(file,\r\n                object : ImageCapture.OnImageSavedListener {\r\n                    override fun onError(error: ImageCapture.UseCaseError,\r\n                                         message: String, exc: Throwable?) {\r\n                        val msg = \"Photo capture failed: $message\"\r\n                        Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()\r\n                        exc?.printStackTrace()\r\n                    }\r\n\r\n                    override fun onImageSaved(file: File) {\r\n                        val msg = \"\uc0ac\uc9c4 \uacbd\ub85c : ${file.absolutePath}\"\r\n                        Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()\r\n                    }\r\n                })\r\n        }\r\n        \/\/\uc0ac\uc9c4\ucc0d\uae30 \uc124\uc815 \ub05d\r\n\r\n        CameraX.bindToLifecycle(this, preview, imageCapture)\r\n    }<\/pre>\n<p>\ub2e4\uc2dc \ube4c\ub4dc \ud558\uace0 \uc2e4\ud589\ud558\uc5ec \uc0ac\uc9c4\uc774 \uc798 \ucc0d\ud788\ub294\uc9c0 \ud655\uc778\ud574\ubcf4\uc138\uc694.<\/p>\n<h3>\uc774\ubbf8\uc9c0 \ud504\ub85c\uc138\uc2f1 \uad6c\ud604\ud558\uae30<\/h3>\n<p><span><span class=\"\">CameraX\uc758 \ub9e4\uc6b0 \ud765\ubbf8\ub85c\uc6b4 \uae30\ub2a5\uc740 ImageAnalysis \ud074\ub798\uc2a4\uc785\ub2c8\ub2e4.\u00a0<\/span><span class=\"\">\ub4e4\uc5b4\uc624\ub294 \uce74\uba54\ub77c \ud504\ub808\uc784\uacfc \ud568\uaed8 \ud638\ucd9c \ub420 ImageAnalysis.Analyzer \uc778\ud130\ud398\uc774\uc2a4\ub97c \uad6c\ud604\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.\u00a0<\/span>\uce74\uba54\ub77c \uc138\uc158 \uc0c1\ud0dc\ub97c \uad00\ub9ac\ud558\uac70\ub098 \uc774\ubbf8\uc9c0\ub97c \ud3d0\uae30\ud558\ub294 \uac83\uc5d0 \ub300\ud574 \uac71\uc815\ud560 \ud544\uc694\uac00 \uc5c6\uc2b5\ub2c8\ub2e4.\u00a0<\/span><\/p>\n<p><span>\uc774 \uc608\uc81c\uc5d0\uc11c\ub294 \uc774\ubbf8\uc9c0\uc758 \ud718\ub3c4\ub97c \ubd84\uc11d\ud558\ub294 \ucee4\uc2a4\ud140 Analyzer\ub97c \ub9cc\ub4e4\uc5b4\ubcf4\ub3c4\ub85d \ud558\uaca0\uc2b5\ub2c8\ub2e4.<\/span><\/p>\n<pre class=\"lang:java decode:true \">private class LuminosityAnalyzer : ImageAnalysis.Analyzer {\r\n    private var lastAnalyzedTimestamp = 0L\r\n    \/**\r\n     * \uc774\ubbf8\uc9c0 \ubc84\ud37c\ub97c \ubc14\uc774\ud2b8 \ubc30\uc5f4\ub85c \ucd94\ucd9c\ud558\uae30 \uc704\ud55c \uc775\uc2a4\ud150\uc158\r\n     *\/\r\n    private fun ByteBuffer.toByteArray(): ByteArray {\r\n        rewind()    \/\/ \ubc84\ud37c\uc758 \ud3ec\uc9c0\uc158\uc744 0\uc73c\ub85c \ub418\ub3cc\ub9bc\r\n        val data = ByteArray(remaining())\r\n        get(data)   \/\/ \ubc14\uc774\ud2b8 \ubc84\ud37c\ub97c \ubc14\uc774\ud2b8 \ubc30\uc5f4\ub85c \ubcf5\uc0ac\ud568\r\n        return data \/\/ \ubc14\uc774\ud2b8 \ubc30\uc5f4 \ubc18\ud658\ud568\r\n    }\r\n    override fun analyze(image: ImageProxy, rotationDegrees: Int) {\r\n        val currentTimestamp = System.currentTimeMillis()\r\n        \/\/ \ub9e4\ud504\ub808\uc784\uc744 \uacc4\uc0b0\ud558\uc9c4 \uc54a\uace0 1\ucd08\ub9c8\ub2e4 \ud55c\ubc88\uc529 \uc815\ub3c4 \uacc4\uc0b0\r\n        if (currentTimestamp - lastAnalyzedTimestamp &gt;= TimeUnit.SECONDS.toMillis(1)) {\r\n            \/\/ \uc774\ubbf8\uc9c0 \ud3ec\ub9f7\uc774 YUV\uc774\ubbc0\ub85c image.planes[0]\uc73c\ub85c Y\uac12\uc744 \uad6c\ud560\uc218 \uc788\ub2e4.\r\n            val buffer = image.planes[0].buffer\r\n            \/\/ \uc774\ubbf8\uc9c0 \ub370\uc774\ud130\ub97c \ubc14\uc774\ud2b8\ubc30\uc5f4\ub85c \ucd94\ucd9c\r\n            val data:ByteArray = buffer.toByteArray()\r\n            \/\/ \ud53d\uc140 \ud558\ub098\ud558\ub098\ub97c \uc720\uc758\ubbf8\ud55c \ub370\uc774\ud130\ub9ac\uc2a4\ud2b8\ub85c \ub9cc\ub4e0\ub2e4\r\n            val pixels:List&lt;Int&gt; = data.map { it.toInt() and 0xFF }\r\n            \/\/ \uc774\ubbf8\uc9c0\uc758 \ud3c9\uade0 \ud718\ub3c4\ub97c \uad6c\ud55c\ub2e4\r\n            val luma:Double = pixels.average()\r\n            \/\/ \ub85c\uadf8\uc5d0 \ud718\ub3c4 \ucd9c\ub825\r\n            Log.d(\"CameraXApp\", \"Average luminosity: $luma\")\r\n            \/\/ \ub9c8\uc9c0\ub9c9 \ubd84\uc11d\ud55c \ud504\ub808\uc784\uc758 \ud0c0\uc784\uc2a4\ud0ec\ud504\ub85c \uc5c5\ub370\uc774\ud2b8\ud55c\ub2e4.\r\n            lastAnalyzedTimestamp = currentTimestamp\r\n        }\r\n    }\r\n}<\/pre>\n<p>\uc704\uc640 \uac19\uc774 ImageAnaysis.Analyzer \uc778\ud130\ud398\uc774\uc2a4\ub97c \uad6c\ud604\ud55c LuminosityAnlyzer\ub97c \ub9cc\ub4e4\uc5c8\uc2b5\ub2c8\ub2e4. \uc774\uc81c LuminosityAnalyzer\ub97c \uc778\uc2a4\ud134\uc2a4\ud654 \ud558\uc5ec startCamera()\uc5d0 \ud3ec\ud568\uc2dc\ud0a4\uba74 \ub429\ub2c8\ub2e4.<\/p>\n<pre class=\"lang:java decode:true\">private fun startCamera() {\r\n    \/\/\ubbf8\ub9ac\ubcf4\uae30 \uc124\uc815\r\n    val preview = ...\r\n    \/\/\uc0ac\uc9c4\ucc0d\uae30 \uc124\uc815\r\n    val imageCapture = ...\r\n    \r\n    \/\/\uc774\ubbf8\uc9c0 \ud504\ub85c\uc138\uc2f1 \uc124\uc815\r\n    val analyzerConfig = ImageAnalysisConfig.Builder().apply {\r\n        \/\/ \uc774\ubbf8\uc9c0 \ubd84\uc11d\uc744 \uc704\ud55c \uc4f0\ub808\ub4dc\ub97c \ud558\ub098 \uc0dd\uc131\ud569\ub2c8\ub2e4.\r\n        val analyzerThread = HandlerThread(\"LuminosityAnalysis\").apply { start() }\r\n        setCallbackHandler(Handler(analyzerThread.looper))\r\n        \/\/ \ud558\ub098\ub3c4 \ube60\uc9d0\uc5c6\uc774 \ud504\ub808\uc784 \uc804\ubd80\ub97c \ubd84\uc11d\ud558\uae30\ubcf4\ub2e4\ub294 \ub9e4\uc21c\uac04 \uac00\uc7a5 \ucd5c\uadfc \ud504\ub808\uc784\ub9cc\uc744 \uac00\uc838\uc640 \ubd84\uc11d\ud558\ub3c4\ub85d \ud569\ub2c8\ub2e4\r\n        setImageReaderMode(ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE)\r\n    }.build()\r\n    \/\/ \ucee4\uc2a4\ud140 \uc774\ubbf8\uc9c0 \ud504\ub85c\uc138\uc2f1 \uac1d\uccb4 \uc0dd\uc131\r\n    val analyzerUseCase = ImageAnalysis(analyzerConfig).apply {\r\n        analyzer = LuminosityAnalyzer()\r\n    }\r\n    \/\/\uc720\uc988\ucf00\uc774\uc2a4\ub4e4\uc744 \ubc14\uc778\ub529\ud568\r\n    CameraX.bindToLifecycle(this, preview, imageCapture, analyzerUseCase)\r\n}<\/pre>\n<p>\uc774\uc81c \ub2e4\uc2dc \ube4c\ub4dc \ud6c4 \uc2e4\ud589\uc744 \ud558\uba74 \ub85c\uadf8\uc5d0 \ud718\ub3c4\uac00 \uc57d 1\ucd08\ub9c8\ub2e4 \ucc0d\ud788\ub294\uac83\uc744 \ud655\uc778\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n<p>\uc804\uccb4 \uc18c\uc2a4\ucf54\ub4dc\ub294 <a href=\"https:\/\/github.com\/Charlezz\/CameraXDemo\">github<\/a>\uc5d0\uc11c \ud655\uc778 \uac00\ub2a5\ud569\ub2c8\ub2e4.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>\uc544\uc9c1 \uc54c\ud30c \ub2e8\uacc4\uc758 \ub77c\uc774\ube0c\ub7ec\ub9ac \uc785\ub2c8\ub2e4 CameraX\ub294 Jetpack\uc5d0 \ud3ec\ud568\ub41c \uc11c\ud3ec\ud2b8 \ub77c\uc774\ube0c\ub7ec\ub9ac\ub85c \uce74\uba54\ub77c \uc571 \uac1c\ubc1c\uc744 \uc27d\uac8c \ud560 \uc218 \uc788\ub3c4\ub85d \ub3c4\uc640 \uc90d\ub2c8\ub2e4. \ud2b9\uc9d5 Camera2\ub97c \uc0ac\uc6a9\ud558\ubbc0\ub85c Android 5.0 (API level 21)\uae4c\uc9c0\ub9cc \uc9c0\uc6d0 \uc720\uc988\ucf00\uc774\uc2a4 \uae30\ubc18\uc73c\ub85c \uc124\uacc4\ub418\uc5b4 Preview, Image Processing, Image Capture \uc720\uc988 \ucf00\uc774\uc2a4 \ub3d9\uc2dc \uc9c0\uc6d0 \uc0dd\uba85\uc8fc\uae30\ub97c \uc778\uc2dd\ud568 \uc7a5\uce58 \ud638\ud658\uc131 \ubb38\uc81c \ud574\uacb0\ud568\uc73c\ub85c \uae30\uae30\ubcc4 \ubd84\uae30\ucf54\ub4dc \uac10\uc18c \ud2b9\uc815 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"om_disable_all_campaigns":false,"inline_featured_image":false,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0},"categories":[24,5],"tags":[],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/charlezz.com\/index.php?rest_route=\/wp\/v2\/posts\/1237"}],"collection":[{"href":"https:\/\/charlezz.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/charlezz.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/charlezz.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/charlezz.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1237"}],"version-history":[{"count":10,"href":"https:\/\/charlezz.com\/index.php?rest_route=\/wp\/v2\/posts\/1237\/revisions"}],"predecessor-version":[{"id":43760,"href":"https:\/\/charlezz.com\/index.php?rest_route=\/wp\/v2\/posts\/1237\/revisions\/43760"}],"wp:attachment":[{"href":"https:\/\/charlezz.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1237"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/charlezz.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1237"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/charlezz.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1237"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}