1 /*
<lambda>null2  * Copyright 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.camera.integration.extensions.utils
18 
19 import android.content.Context
20 import android.graphics.ImageFormat
21 import android.graphics.Point
22 import android.graphics.SurfaceTexture
23 import android.hardware.camera2.CameraCharacteristics
24 import android.hardware.camera2.CameraExtensionCharacteristics
25 import android.hardware.camera2.CameraManager
26 import android.os.Build
27 import android.util.DisplayMetrics
28 import android.util.Log
29 import android.util.Size
30 import androidx.annotation.RequiresApi
31 import androidx.camera.integration.extensions.EXTENSION_MODE_NONE
32 import java.util.stream.Collectors
33 
34 private const val TAG = "Camera2ExtensionsUtil"
35 
36 /** Util functions for Camera2 Extensions implementation */
37 object Camera2ExtensionsUtil {
38 
39     /** Camera2 extension modes */
40     @Suppress("DEPRECATION") // EXTENSION_BEAUTY
41     @RequiresApi(31)
42     @JvmStatic
43     val AVAILABLE_CAMERA2_EXTENSION_MODES =
44         arrayOf(
45             CameraExtensionCharacteristics.EXTENSION_AUTOMATIC,
46             CameraExtensionCharacteristics.EXTENSION_BEAUTY,
47             CameraExtensionCharacteristics.EXTENSION_BOKEH,
48             CameraExtensionCharacteristics.EXTENSION_HDR,
49             CameraExtensionCharacteristics.EXTENSION_NIGHT,
50         )
51 
52     /** Converts extension mode from integer to string. */
53     @Suppress("DEPRECATION") // EXTENSION_BEAUTY
54     @RequiresApi(31)
55     @JvmStatic
56     fun getCamera2ExtensionModeStringFromId(extension: Int): String =
57         when (extension) {
58             EXTENSION_MODE_NONE -> "None"
59             CameraExtensionCharacteristics.EXTENSION_HDR -> "HDR"
60             CameraExtensionCharacteristics.EXTENSION_NIGHT -> "NIGHT"
61             CameraExtensionCharacteristics.EXTENSION_BOKEH -> "BOKEH"
62             CameraExtensionCharacteristics.EXTENSION_BEAUTY -> "FACE RETOUCH"
63             CameraExtensionCharacteristics.EXTENSION_AUTOMATIC -> "AUTO"
64             else -> throw IllegalArgumentException("Invalid extension mode id!")
65         }
66 
67     @Suppress("DEPRECATION") // EXTENSION_BEAUTY
68     @RequiresApi(31)
69     @JvmStatic
70     fun getCamera2ExtensionModeIdFromString(mode: String): Int =
71         when (mode) {
72             "None" -> EXTENSION_MODE_NONE
73             "HDR" -> CameraExtensionCharacteristics.EXTENSION_HDR
74             "NIGHT" -> CameraExtensionCharacteristics.EXTENSION_NIGHT
75             "BOKEH" -> CameraExtensionCharacteristics.EXTENSION_BOKEH
76             "FACE RETOUCH" -> CameraExtensionCharacteristics.EXTENSION_BEAUTY
77             "AUTO" -> CameraExtensionCharacteristics.EXTENSION_AUTOMATIC
78             else -> throw IllegalArgumentException("Invalid extension mode string!")
79         }
80 
81     /** Gets the first camera id of the specified lens facing. */
82     @JvmStatic
83     fun getLensFacingCameraId(cameraManager: CameraManager, lensFacing: Int): String {
84         cameraManager.cameraIdList.forEach { cameraId ->
85             val characteristics = cameraManager.getCameraCharacteristics(cameraId)
86             if (characteristics[CameraCharacteristics.LENS_FACING] == lensFacing) {
87                 characteristics[CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES]?.let {
88                     if (
89                         it.contains(
90                             CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE
91                         )
92                     ) {
93                         return cameraId
94                     }
95                 }
96             }
97         }
98 
99         throw IllegalArgumentException("Can't find camera of lens facing $lensFacing")
100     }
101 
102     @RequiresApi(31)
103     @JvmStatic
104     fun isCamera2ExtensionModeSupported(
105         context: Context,
106         cameraId: String,
107         extensionMode: Int
108     ): Boolean {
109         val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
110         val extensionCharacteristics = cameraManager.getCameraExtensionCharacteristics(cameraId)
111         return extensionCharacteristics.supportedExtensions.contains(extensionMode)
112     }
113 
114     /**
115      * Picks a preview resolution that is both close/same as the display size and supported by
116      * camera and extensions.
117      */
118     @RequiresApi(Build.VERSION_CODES.S)
119     @JvmStatic
120     fun pickPreviewResolution(
121         cameraManager: CameraManager,
122         cameraId: String,
123         displayMetrics: DisplayMetrics,
124         extensionMode: Int
125     ): Size? {
126         val characteristics = cameraManager.getCameraCharacteristics(cameraId)
127         val map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
128         val textureSizes = map!!.getOutputSizes(SurfaceTexture::class.java)
129         val displaySize = Point()
130         displaySize.x = displayMetrics.widthPixels
131         displaySize.y = displayMetrics.heightPixels
132         if (displaySize.x < displaySize.y) {
133             displaySize.x = displayMetrics.heightPixels
134             displaySize.y = displayMetrics.widthPixels
135         }
136         val displayArRatio = displaySize.x.toFloat() / displaySize.y
137         val previewSizes = ArrayList<Size>()
138         var previewSize: Size? = null
139         var currentDistance = Int.MAX_VALUE
140         for (sz in textureSizes) {
141             val arRatio = sz.width.toFloat() / sz.height
142             if (Math.abs(arRatio - displayArRatio) <= .2f) {
143                 previewSizes.add(sz)
144             }
145             val distance = Math.abs(sz.width * sz.height - displaySize.x * displaySize.y)
146             if (currentDistance > distance) {
147                 currentDistance = distance
148                 previewSize = sz
149             }
150         }
151 
152         if (previewSizes.isEmpty()) {
153             previewSize?.let { previewSizes.add(it) }
154                 ?: throw IllegalStateException("No preview size was found")
155         } else {
156             previewSize = previewSizes[0]
157         }
158 
159         if (extensionMode == EXTENSION_MODE_NONE) {
160             for (sz in previewSizes) {
161                 val distance = Math.abs(sz.width * sz.height - displaySize.x * displaySize.y)
162                 if (currentDistance > distance) {
163                     currentDistance = distance
164                     previewSize = sz
165                 }
166             }
167             return previewSize
168         }
169 
170         val extensionCharacteristics = cameraManager.getCameraExtensionCharacteristics(cameraId)
171         val extensionSizes =
172             extensionCharacteristics.getExtensionSupportedSizes(
173                 extensionMode,
174                 SurfaceTexture::class.java
175             )
176         if (extensionSizes.isEmpty()) {
177             return null
178         }
179 
180         previewSize = extensionSizes[0]
181         val supportedPreviewSizes =
182             previewSizes
183                 .stream()
184                 .distinct()
185                 .filter { o: Size -> extensionSizes.contains(o) }
186                 .collect(Collectors.toList())
187         if (supportedPreviewSizes.isNotEmpty()) {
188             currentDistance = Int.MAX_VALUE
189             for (sz in supportedPreviewSizes) {
190                 val distance = Math.abs(sz.width * sz.height - displaySize.x * displaySize.y)
191                 if (currentDistance > distance) {
192                     currentDistance = distance
193                     previewSize = sz
194                 }
195             }
196         } else {
197             Log.w(
198                 TAG,
199                 "No overlap between supported camera and extensions preview sizes using" +
200                     " first available!"
201             )
202         }
203 
204         return previewSize
205     }
206 
207     /** Picks a resolution for still image capture. */
208     @RequiresApi(Build.VERSION_CODES.S)
209     @JvmStatic
210     fun pickStillImageResolution(
211         cameraCharacteristics: CameraCharacteristics,
212         extensionCharacteristics: CameraExtensionCharacteristics,
213         extensionMode: Int
214     ): Pair<Size, Int> {
215         val yuvColorEncodingSystemSizes =
216             if (extensionMode == EXTENSION_MODE_NONE) {
217                 cameraCharacteristics
218                     .get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!
219                     .getOutputSizes(ImageFormat.YUV_420_888)
220                     .toList()
221             } else {
222                 extensionCharacteristics.getExtensionSupportedSizes(
223                     extensionMode,
224                     ImageFormat.YUV_420_888
225                 )
226             }
227         val jpegSizes =
228             if (extensionMode == EXTENSION_MODE_NONE) {
229                 cameraCharacteristics
230                     .get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!
231                     .getOutputSizes(ImageFormat.JPEG)
232                     .toList()
233             } else {
234                 extensionCharacteristics.getExtensionSupportedSizes(extensionMode, ImageFormat.JPEG)
235             }
236         val stillFormat = if (jpegSizes.isEmpty()) ImageFormat.YUV_420_888 else ImageFormat.JPEG
237         val stillCaptureSize =
238             if (jpegSizes.isEmpty()) yuvColorEncodingSystemSizes[0] else jpegSizes[0]
239 
240         return Pair(stillCaptureSize, stillFormat)
241     }
242 }
243